/*---------------------------------------------------------------------------*
 |          Copyright (c) 2005-2022 PDFlib GmbH. All rights reserved.        |
 +---------------------------------------------------------------------------+
 |    This software may not be copied or distributed except as expressly     |
 |    authorized by PDFlib GmbH's general license agreement or a custom      |
 |    license agreement signed by PDFlib GmbH.                               |
 |    For more information about licensing please refer to www.pdflib.com.   |
 *---------------------------------------------------------------------------*/

//
// C++ wrapper for TET
//

#ifndef TETLIB_HPP
#define TETLIB_HPP

#include <string>
#include <sstream>
#include <iostream>
#include <stdexcept>

/* figure out whether or not we're running on an EBCDIC-based machine */
#define TETCPP_ASCII_A          0x41
#define TETCPP_PLATFORM_A       'A'
#define TETCPP_EBCDIC_A         0xC1

#if (TETCPP_ASCII_A != TETCPP_PLATFORM_A \
        && TETCPP_EBCDIC_A == TETCPP_PLATFORM_A)
#define TETCPP_INTERNAL_OPTFORMAT "ebcdicutf8"
#else
#define TETCPP_INTERNAL_OPTFORMAT "utf8"
#endif

// We use TET as a C++ class name, therefore hide the actual C struct
// name for TET usage with C++.
typedef struct TET_s TET_cpp;
#define TET TET_cpp
#include "tetlib.h"
#undef TET

#ifdef TRN_HAS_PDFLIB
#endif


/*
 * TETCPP_DL
 *
 * The tet.hpp header can be used for static linking against the TET library,
 * or it can be configured for loading the TET DLL dynamically at runtime.
 *
 * The default is to compile for static linking against the TET library. For
 * dynamic loading, define TETCPP_DL as 1. In that case the resulting program
 * must not be linked against the TET library. Instead the tetlibdl.c module from
 * the "bind/c" directory must be compiled and linked to the application.
 */
#ifndef TETCPP_DL
#define TETCPP_DL 0
#endif

#if TETCPP_DL
#include "tetlibdl.h"
#endif

namespace pdflib {

// The C++ class wrapper for TET

#if defined(_MSC_VER)
// Suppress Visual C++ warnings about ignored exception specifications.
#pragma warning(disable: 4290)
#endif

template<class pstring, class conv> class basic_TET;

/**
 * The "do-nothing" converter that has the effect that the basic_TET class
 * behaves in the same way as the TET 3.0 wrapper.
 */
template<class pstring>
class TETNoOpConverter
{
public:
    static bool do_conversion()
    {
        return false;
    }

    static void convert_to_pdf_bytes(
        const basic_TET<pstring, TETNoOpConverter<pstring> >&,
        const pstring&, std::string&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_bytes: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pdf_utf8(
            const basic_TET<pstring, TETNoOpConverter<pstring> >&,
            const pstring&, std::string&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_utf8: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pdf_utf16(
            const basic_TET<pstring, TETNoOpConverter<pstring> >&,
            const pstring&, std::string&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pdf_utf16: internal error: "
                "converter called although do_conversion() returns false");
    }

    static void convert_to_pstring(
            const basic_TET<pstring, TETNoOpConverter<pstring> >&,
            const char *, pstring&)
    {
        throw std::logic_error(
                "TETNoOpConverter::convert_to_pstring: internal error: "
                "converter called although do_conversion() returns false");
    }
};

#if TETCPP_DL
#define MTETLIB_API m_TETlib_api->

#define TETCPP_TRY      TET_TRY_DL(m_TETlib_api, tet)
#define TETCPP_CATCH  \
TET_CATCH_DL(m_TETlib_api, tet) {\
    pstring message, apiname; \
    convert_exception_strings(message, apiname); \
    throw Exception(message, m_TETlib_api->TET_get_errnum(tet), apiname, \
                        m_TETlib_api->TET_get_opaque(tet)); \
}

#else // TETCPP_DL
#define MTETLIB_API

#define TETCPP_TRY      TET_TRY(tet)
#define TETCPP_CATCH  \
TET_CATCH(tet) {\
    pstring message, apiname; \
    convert_exception_strings(tet, message, apiname); \
    throw Exception(message, TET_get_errnum(tet), apiname, \
                        TET_get_opaque(tet)); \
}

#endif // TETCPP_DL

/**
 * Function pointers for callback APIs. These are pointers to functions
 * with "C" linkage.
 */
extern "C"
{
    typedef size_t (* TET_callback_readproc)(void *opaque, void *buffer, size_t size);
    typedef int (* TET_callback_seekproc)(void *opaque, tet_off_t offset);
    typedef int (* TET_callback_interruptproc)(void *opaque);
}

template<class pstring, class conv>
class basic_TET
{
    friend class TETNoOpConverter<pstring>;

public:
    /* The following enums are identical to the corresponding C enums
     * in tetlib.h except for the TET_ prefix.
     */
    /* Values for the "type" field of TET_char_info */
    enum
    {
        /* Normal character represented by exactly one glyph */
        CT_NORMAL       = 0,

        /* Start of a sequence, e.g. ligature */
        CT_SEQ_START    = 1,

        /* Continuation of a sequence */
        CT_SEQ_CONT     = 10,

        /* Inserted word, line, or paragraph separator */
        CT_INSERTED     = 12
    };

    /* Bit values for the "attributes" field of TET_char_info */
    enum
    {
        ATTR_NONE         = 0x00000000, /* no attribute set         */
        ATTR_SUB          = 0x00000001, /* subscript                */
        ATTR_SUP          = 0x00000002, /* superscript              */
        ATTR_DROPCAP      = 0x00000004, /* initial large letter     */
        ATTR_SHADOW       = 0x00000008, /* shadowed text            */

        /* Character before hyphenation */
        ATTR_DEHYPHENATION_PRE       = 0x00000010,

        /* Hyphenation character, i.e. soft hyphen (unrelated to Tagged PDF Artifact) */
        ATTR_DEHYPHENATION_ARTIFACT  = 0x00000020,

        /* Character after hyphenation */
        ATTR_DEHYPHENATION_POST      = 0x00000040,

        /*
         * Text or image marked as Artifact (irrelevant content) in Tagged PDF
         * (used in TET_char_info and TET_image_info)
         */
        ATTR_ARTIFACT                = 0x00000100
    };


    /*
     * The following bit values are only used in the "attributes" field of
     * TET_image_info
     */
    enum
    {
        /* Image extracted from an annotation (appearance stream) */
        ATTR_ANNOTATION              = 0x00000200,

        /* Image extracted from a pattern */
        ATTR_PATTERN                 = 0x00000400,

        /*
         * Image extracted from a soft mask in a graphics state (defined in a
         * Transparency Group XObject)
         */
        ATTR_SOFTMASK                = 0x00000800
    };


    /*
     * Text rendering modes used in the "textrendering" field of TET_char_info
     */
    enum
    {
        TR_FILL             = 0,       /* fill text                         */
        TR_STROKE           = 1,       /* stroke text (outline)             */
        TR_FILLSTROKE       = 2,       /* fill and stroke text              */
        TR_INVISIBLE        = 3,       /* invisible text                    */
        TR_FILL_CLIP        = 4,       /* fill text and
                                          add it to the clipping path       */
        TR_STROKE_CLIP      = 5,       /* stroke text and
                                          add it to the clipping path       */
        TR_FILLSTROKE_CLIP  = 6,       /* fill and stroke text and
                                          add it to the clipping path       */
        TR_CLIP             = 7        /* add text to the clipping path     */
    };


    /* Image formats returned by TET_write_image_file() */
    enum
    {                           /* MIME type and file name suffix            */
        IF_TIFF  = 10,          /* image/tiff, *.tif                         */
        IF_JPEG  = 20,          /* image/jpeg, *.jpg                         */
        IF_JP2   = 31,          /* image/jp2, *.jp2                          */
        IF_JPF   = 32,          /* image/jpx, *.jpf                          */
        IF_J2K   = 33,          /* raw JPEG 2000 code stream, *.j2k          */
        IF_JBIG2 = 50           /* image/x-jbig2, *.jbig2                    */
    };

    /* The string type used for option lists and return values. */
    typedef pstring string_type;

    class Exception
    {
    public:
        Exception(const pstring& errmsg, int errnum, const pstring& apiname,
                    void *opaque) :
            m_errmsg(errmsg),
              m_errnum(errnum),
              m_apiname(apiname),
              m_opaque(opaque)
        {
        }

        pstring get_errmsg() const { return m_errmsg; }
        int get_errnum() const { return m_errnum; }
        pstring get_apiname() const { return m_apiname; }
        const void *get_opaque() const { return m_opaque; }
    private:
        pstring m_errmsg;
        int m_errnum;
        pstring m_apiname;
        void * m_opaque;
    };

#if TETCPP_DL
    class dl_load_error: public std::runtime_error
    {
    public:
        explicit dl_load_error() :
            std::runtime_error("Couldn't load TET DLL")
        {
        }
    };

    /*
     * The dynamic loading variant of the constructor accepts the "opaque"
     * parameter, but requires it to be NULL, as the opaque pointer is used
     * internally by TET_new_dl().
     */
    basic_TET(void *opaque = NULL)
    {
        if (opaque)
        {
            throw std::invalid_argument(
                    "In the dynamic loading variant of the TET C++ binding "
                    "the 'opaque' parameter must be NULL");
        }

        m_TETlib_api = TET_new_dl(&tet);

        if (!m_TETlib_api)
        {
            throw dl_load_error();
        }

        check_api(NULL);

        set_cpp_binding_options();
    }
    
    ~basic_TET()
    {
        TET_delete_dl(m_TETlib_api, tet);
    }
#else /* TETCPP_DL */
    basic_TET(void *opaque = NULL)
    {
        tet = TET_new2(NULL, opaque);
        if (!tet)
        {
            throw std::bad_alloc();
        }

        set_cpp_binding_options();
    }

    ~basic_TET()
    {
        TET_delete(tet);
    }
#endif /* TETCPP_DL */



/* Release a document handle and all internal resources related to that document */
void
close_document(int doc)
{
    TETCPP_TRY {
	MTETLIB_API TET_close_document(tet, doc);
    }
    TETCPP_CATCH;
}


/* Release a page handle and all related resources. */
void
close_page(int page)
{
    TETCPP_TRY {
	MTETLIB_API TET_close_page(tet, page);
    }
    TETCPP_CATCH;
}


/* Create a named virtual read-only file from data provided in memory. */
void
create_pvf(const pstring& filename, const void * data, size_t size, const pstring& optlist)
{
    std::string filename_param;
    const char *p_filename_param;
    int len_filename;
    param_to_0utf16(filename, filename_param, p_filename_param, len_filename);
    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	MTETLIB_API TET_create_pvf(tet, p_filename_param, len_filename, data, size, p_optlist_param);
    }
    TETCPP_CATCH;
}


/* Delete a named virtual file and free its data structures. */
int
delete_pvf(const pstring& filename)
{
    int volatile retval = 0;

    std::string filename_param;
    const char *p_filename_param;
    int len_filename;
    param_to_0utf16(filename, filename_param, p_filename_param, len_filename);

    TETCPP_TRY {
	retval = MTETLIB_API TET_delete_pvf(tet, p_filename_param, len_filename);
    }
    TETCPP_CATCH;

    return retval;
}


/* Get the name of the API function which caused an exception or failed. */
pstring
get_apiname()
{
    const char * volatile retval = NULL;
    pstring pstring_retval;

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_apiname(tet);
    }
    TETCPP_CATCH;

    apiretval_to_pstring(retval, pstring_retval);

    return pstring_retval;
}


/* Get the text of the last thrown exception or the reason for a failed function call. */
pstring
get_errmsg()
{
    const char * volatile retval = NULL;
    pstring pstring_retval;

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_errmsg(tet);
    }
    TETCPP_CATCH;

    apiretval_to_pstring(retval, pstring_retval);

    return pstring_retval;
}


/* Get the number of the last thrown exception or the reason for a failed function call. */
int
get_errnum()
{
    int volatile retval = 0;

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_errnum(tet);
    }
    TETCPP_CATCH;

    return retval;
}


/* Write image data to memory. */
const char *
get_image_data(int doc, size_t *outputlen, int imageid, const pstring& optlist)
{
    const char *volatile retval = NULL;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_image_data(tet, doc, outputlen, imageid, p_optlist_param);
    }
    TETCPP_CATCH;

    return (const char*)retval;
}


/* Get the next text fragment from a page's content. */
pstring
get_text(int page)
{
    const char * volatile retval = NULL;
    int len;
    int *outputlen = &len;
    pstring pstring_retval;

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_text(tet, page, outputlen);
    }
    TETCPP_CATCH;

      if (retval)
      {
          if (conv::do_conversion())
          {
              apiretval_to_pstring(retval, pstring_retval);
          }
          else
          {
              switch (sizeof(typename pstring::value_type))
              {
              case sizeof(char):
              case utf16_wchar_t_size:
              case utf32_wchar_t_size:
                  outputstring_to_pstring(retval, pstring_retval, len);
                  break;

              default:
                  bad_wchar_size("basic_TET<pstring, conv>::get_text");
              }
          }
      }

      return pstring_retval;
}


/* Query properties of a virtual file or the PDFlib Virtual Filesystem (PVF). */
double
info_pvf(const pstring& filename, const pstring& keyword)
{
    double volatile retval = 0;

    std::string filename_param;
    const char *p_filename_param;
    int len_filename;
    param_to_0utf16(filename, filename_param, p_filename_param, len_filename);
    std::string keyword_param;
    const char *p_keyword_param;
    param_to_bytes(keyword, keyword_param, p_keyword_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_info_pvf(tet, p_filename_param, len_filename, p_keyword_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Open a disk-based or virtual PDF document for content extraction. */
int
open_document(const pstring& filename, const pstring& optlist)
{
    int volatile retval = 0;

    std::string filename_param;
    const char *p_filename_param;
    int len_filename;
    param_to_0utf16(filename, filename_param, p_filename_param, len_filename);
    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_open_document(tet, p_filename_param, len_filename, p_optlist_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Open a page for text extraction. */
int
open_page(int doc, int pagenumber, const pstring& optlist)
{
    int volatile retval = 0;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_open_page(tet, doc, pagenumber, p_optlist_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Get the value of a pCOS path with type number or boolean. */
double
pcos_get_number(int doc, const pstring& path)
{
    double volatile retval = 0;

    std::string path_param;
    const char *p_path_param;
    param_to_utf8(path, path_param, p_path_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_pcos_get_number(tet, doc, "%s", p_path_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Get the value of a pCOS path with type name, number, string, or boolean. */
pstring
pcos_get_string(int doc, const pstring& path)
{
    const char * volatile retval = NULL;
    pstring pstring_retval;

    std::string path_param;
    const char *p_path_param;
    param_to_utf8(path, path_param, p_path_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_pcos_get_string(tet, doc, "%s", p_path_param);
    }
    TETCPP_CATCH;

    apiretval_to_pstring(retval, pstring_retval);

    return pstring_retval;
}


/* Get the contents of a pCOS path with type stream, fstream, or string. */
const unsigned char *
pcos_get_stream(int doc, int *outputlen, const pstring& optlist, const pstring& path)
{
    const unsigned char *volatile retval = NULL;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);
    std::string path_param;
    const char *p_path_param;
    param_to_utf8(path, path_param, p_path_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_pcos_get_stream(tet, doc, outputlen, p_optlist_param, "%s", p_path_param);
    }
    TETCPP_CATCH;

    return (const unsigned char*)retval;
}


/* Set one or more global options for TET. */
void
set_option(const pstring& optlist)
{
    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	MTETLIB_API TET_set_option(tet, p_optlist_param);
    }
    TETCPP_CATCH;
}


/* Write image data to disk. */
int
write_image_file(int doc, int imageid, const pstring& optlist)
{
    int volatile retval = 0;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_write_image_file(tet, doc, imageid, p_optlist_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Process a page and create TETML output. */
int
process_page(int doc, int pageno, const pstring& optlist)
{
    int volatile retval = 0;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_process_page(tet, doc, pageno, p_optlist_param);
    }
    TETCPP_CATCH;

    return retval;
}


/* Retrieve TETML data from memory. */
const char *
get_tetml(int doc, size_t *outputlen, const pstring& optlist)
{
    const char *volatile retval = NULL;

    std::string optlist_param;
    const char *p_optlist_param;
    param_to_utf8(optlist, optlist_param, p_optlist_param);

    TETCPP_TRY {
	retval = MTETLIB_API TET_get_tetml(tet, doc, outputlen, p_optlist_param);
    }
    TETCPP_CATCH;

    return (const char*)retval;
}

    const TET_char_info * get_char_info(int page)
    {
        const TET_char_info * volatile retval = 0;

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_get_char_info(tet, page);
        }
        TETCPP_CATCH;

        return retval;
    }

    const TET_color_info * get_color_info(int doc, int colorid, const pstring& keyword)
    {
        const TET_color_info * volatile retval = 0;
        std::string keyword_param;
        const char *p_keyword_param;
        param_to_utf8(keyword, keyword_param, p_keyword_param);

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_get_color_info(tet, doc, colorid, p_keyword_param);
        }
        TETCPP_CATCH;

        return retval;
    }

    void * get_opaque()
    {
        void * volatile retval = NULL;

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_get_opaque(tet);
        }
        TETCPP_CATCH;

        return retval;
    }

    const TET_image_info * get_image_info(int page)
    {
        const TET_image_info * volatile retval = 0;

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_get_image_info(tet, page);
        }
        TETCPP_CATCH;

        return retval;
    }

    int open_document_callback(void *opaque, tet_off_t filesize,
        TET_callback_readproc readproc, TET_callback_seekproc seekproc,
        const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int volatile retval = 0;

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_open_document_callback(tet, opaque, filesize,
                    readproc, seekproc, p_optlist_param);
        }
        TETCPP_CATCH;

        return retval;
    }

    int open_document_interrupt(void *opaque, tet_off_t filesize,
        TET_callback_readproc readproc, TET_callback_seekproc seekproc,
        TET_callback_interruptproc interruptproc, const pstring& optlist)
    {
        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        int volatile retval = 0;

        TETCPP_TRY
        {
            retval = MTETLIB_API TET_open_document_interrupt(tet, opaque, filesize,
                    readproc, seekproc, interruptproc, p_optlist_param);
        }
        TETCPP_CATCH;

        return retval;
    }

    /* Convert a string in an arbitrary encoding to a Unicode string in various formats.
     */
    std::string
    convert_to_unicode(const pstring& inputformat, const std::string& inputstring, const pstring& optlist) const
    {
        std::string retval;

        std::string inputformat_param;
        const char *p_inputformat_param;
        param_to_bytes(inputformat, inputformat_param, p_inputformat_param);

        std::string optlist_param;
        const char *p_optlist_param;
        param_to_utf8(optlist, optlist_param, p_optlist_param);

        TETCPP_TRY
        {
            int outputlen;
            const char * const buf =
                    MTETLIB_API TET_convert_to_unicode(tet, p_inputformat_param,
                        inputstring.data(), static_cast<int>(inputstring.length()),
                        &outputlen, p_optlist_param);
            if (buf)
                retval.assign(buf, static_cast<size_t>(outputlen));
        }
        TETCPP_CATCH;

        return retval;
    }


protected:
#if TETCPP_DL
    const TET_api *m_TETlib_api;
#endif // TETCPP_DL
    TET_cpp *tet;

private:
    void set_cpp_binding_options(void)
    {
        TETCPP_TRY
        {
            MTETLIB_API TET_set_option(tet, "objorient");
            if (conv::do_conversion())
            {
                MTETLIB_API TET_set_option(tet,
                        "binding={C++ conv} unicaplang=true outputformat=utf8 "
                            "apitextformat=utf8");
            }
            else
            {
                switch (sizeof(typename pstring::value_type))
                {
                case sizeof(char):
                    MTETLIB_API TET_set_option(tet,
                            "binding={C++} outputformat=utf8 "
                            "apitextformat=utf8");
                    break;

                case utf16_wchar_t_size:
                    MTETLIB_API TET_set_option(tet,
                            "binding={C++} unicaplang=true outputformat=utf16 "
                            "apitextformat=utf16");
                    break;

                case utf32_wchar_t_size:
                    MTETLIB_API TET_set_option(tet,
                        "binding={C++} unicaplang=true outputformat=utf32 "
                        "apitextformat=utf32");
                    break;

                default:
                    bad_wchar_size("basic_TET<pstring, conv>::set_cpp_binding_options");
                }
            }
        }
#if TETCPP_DL
        TET_CATCH_DL(m_TETlib_api, tet) {
            pstring message, apiname;

            prepare_bad_env_diagnostics(message, apiname);

            throw Exception(message, 999, apiname, m_TETlib_api->TET_get_opaque(tet));
        }
#else // TETCPP_DL
        TET_CATCH(tet) {
            pstring message, apiname;

            prepare_bad_env_diagnostics(message, apiname);

            throw Exception(message, 998, apiname, TET_get_opaque(tet));
        }
#endif // TETCPP_DL
    }

    /**
     * Prepare the special diagnostic information for the situation that
     * the constructor fails because of a syntactically incorrect TETLOGGING
     * environment variable or from the corresponding Windows registry entry.
     */
    void prepare_bad_env_diagnostics(pstring& message, pstring& apiname)
    {
        switch (sizeof(typename pstring::value_type))
        {
        case sizeof(char):
            apiretval_to_pstring("Syntax error in environment variable 'TETLOGGING'"
#ifdef _WIN32
                    " or in the Windows registry entry 'tetlogging'"
#endif
                    , message);
            apiretval_to_pstring("constructor TET", apiname);
            break;

        case utf16_wchar_t_size:
        case utf32_wchar_t_size:
            apiretval_to_pstring(reinterpret_cast<const char *>(
                    L"Syntax error in environment variable 'TETLOGGING'"
#ifdef _WIN32
                    L" or in the Windows registry entry 'tetlogging'"
#endif
                    ), message);
            apiretval_to_pstring(reinterpret_cast<const char *>(
                    L"constructor TET"), apiname);
            break;
        }
    }

#if TETCPP_DL
    void check_api(void *opaque)
    {
        if (m_TETlib_api->sizeof_TET_api != sizeof(TET_api) ||
                m_TETlib_api->major != TET_MAJORVERSION ||
                m_TETlib_api->minor != TET_MINORVERSION)
        {
            pstring message;
            pstring apiname; /* stays empty */

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                apiretval_to_pstring("loaded wrong version of TET library", message);
                break;

            case utf16_wchar_t_size:
            case utf32_wchar_t_size:
                apiretval_to_pstring(reinterpret_cast<const char *>(L"loaded wrong version of TET library"), message);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::check_api");
            }

            throw Exception(message, -1, apiname, opaque);
        }
    }
#endif // TETCPP_DL

    enum
    {
        utf16_wchar_t_size = 2,
        utf32_wchar_t_size = 4
    };

    void bad_wchar_size(const char *apiname) const
    {
        std::ostringstream exception_text;
        exception_text << apiname << ": unsupported wchar_t size: "
                        << sizeof(typename pstring::value_type);

        throw std::logic_error(exception_text.str());
    }

    void param_to_utf8(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf8(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
        }
        else
        {
            const char * const s = reinterpret_cast<const char *>(param.c_str());
            int outputlen;

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * UTF-8: Pass through user-supplied string directly.
                 */
                tet_ptr = s;
                break;

            case utf16_wchar_t_size:
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf16",
                        s, static_cast<int>(param.length() * utf16_wchar_t_size),
                        &outputlen, "outputformat=" TETCPP_INTERNAL_OPTFORMAT);

                break;

            case utf32_wchar_t_size:
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf32",
                        s, static_cast<int>(param.length() * utf32_wchar_t_size),
                        &outputlen, "outputformat=" TETCPP_INTERNAL_OPTFORMAT);
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf8");
            }
        }
    }

    void param_to_0utf16(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr, int& len) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf16(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
            len = static_cast<int>(tet_param.length());
        }
        else
        {
            const char * const s = reinterpret_cast<const char *>(param.c_str());

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * UTF-8 encoding.
                 */
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf8",
                        s, static_cast<int>(param.length()),
                        &len, "outputformat=utf16");
                break;

            case utf16_wchar_t_size:
                /*
                 * UTF-16 can be passed through directly.
                 */
                tet_ptr = s;
                len = static_cast<int>(param.length() * utf16_wchar_t_size);
                break;

            case utf32_wchar_t_size:
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf32",
                        s, static_cast<int>(param.length() * utf32_wchar_t_size),
                        &len, "outputformat=utf16");

                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_0utf16");
            }
        }
    }

    void param_to_utf16(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr, int& len) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_utf16(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
            len = static_cast<int>(tet_param.length());
        }
        else
        {
            const char * const s = reinterpret_cast<const char *>(param.c_str());

            switch (sizeof(typename pstring::value_type))
            {
            case sizeof(char):
                /*
                 * UTF-8 encoded.
                 */
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf8",
                        s, static_cast<int>(param.length()),
                        &len, "outputformat=utf16");
                break;

            case utf16_wchar_t_size:
                /*
                 * UTF-16 can be passed through directly.
                 */
                tet_ptr = s;
                len = static_cast<int>(param.length() * utf16_wchar_t_size);
                break;

            case utf32_wchar_t_size:
                TET_set_internal_option(tet, "wrappercall=true");
                tet_ptr =
                    MTETLIB_API TET_convert_to_unicode(tet, "utf32",
                        s, static_cast<int>(param.length() * utf32_wchar_t_size),
                        &len, "outputformat=utf16");
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_utf16");
            }
        }
    }

    void param_to_bytes(const pstring& param, std::string& tet_param,
                        const char *& tet_ptr) const
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pdf_bytes(*this, param, tet_param);
            tet_ptr = tet_param.c_str();
        }
        else
        {
            const size_t size = sizeof(typename pstring::value_type);
            const char *s = reinterpret_cast<const char *>(param.c_str());

            switch (size)
            {
            case sizeof(char):
                tet_ptr = s;
                break;

            case utf16_wchar_t_size:
            case utf32_wchar_t_size:
                {
                    int highchar;

                    const char *deflated =
                            MTETLIB_API TET_deflate_unicode(tet, s,
                                    static_cast<int>(param.length() * size),
                                    size, &highchar);

                    if (!deflated)
                    {
                        std::ostringstream exception_text;

                        exception_text
                            << "basic_TET::param_to_bytes: high "
                                "Unicode character '0x"
                            << std::hex << highchar
                            << "' is not supported in this character string";

                        throw std::runtime_error(exception_text.str().c_str());
                    }

                    tet_ptr = deflated;
                }
                break;

            default:
                bad_wchar_size("basic_TET<pstring, conv>::param_to_bytes");
            }
        }
    }

    void apiretval_to_pstring(const char * const tet_retval,
                        pstring& cpp_retval) const
    {
        if (conv::do_conversion())
        {
            if (tet_retval)
            {
                conv::convert_to_pstring(*this, tet_retval, cpp_retval);
            }
            else
            {
                cpp_retval.erase();
            }
        }
        else
        {
            if (tet_retval)
            {
                cpp_retval.assign(reinterpret_cast
                                    <const typename pstring::value_type *>
                                        (tet_retval));
            }
            else
            {
                cpp_retval.erase();
            }
        }
    }

    /**
     * Separate routine for converting output strings to pstrings. This is used
     * for the return value of get_text(), where the length is needed because
     * the wchar_t strings are not 0-terminated.
     * Is never called if conv::do_conversion() returns true.
     */
    void outputstring_to_pstring(const char * const tet_retval,
                        pstring& cpp_retval, const int length) const
    {
        if (tet_retval)
        {
            cpp_retval.assign(reinterpret_cast
                                <const typename pstring::value_type *>
                                    (tet_retval),
                                length);
        }
        else
        {
            cpp_retval.erase();
        }
    }
    
    void
    convert_exception_strings(pstring& message, pstring& apiname) const
    {
        convert_exception_strings(*this, message, apiname);
    }

    static
    void
    convert_exception_strings(basic_TET const& tet_cpp,
            pstring& message, pstring& apiname)
    {
        if (conv::do_conversion())
        {
            conv::convert_to_pstring(tet_cpp, MTETLIB_API TET_get_errmsg(tet_cpp.tet),
                message);
            conv::convert_to_pstring(tet_cpp, MTETLIB_API TET_get_apiname(tet_cpp.tet),
                apiname);
        }
        else
        {
            /*
             * Without custom converter the TET API returns the error message
             * and the API name in the expected encoding, so just put this
             * string into the output string.
             */
            message = reinterpret_cast<const typename pstring::value_type*>
                (MTETLIB_API TET_get_errmsg(tet_cpp.tet));
            apiname = reinterpret_cast<const typename pstring::value_type*>
                (MTETLIB_API TET_get_apiname(tet_cpp.tet));
        }
    }


    // Prevent use of copy constructor and assignment operator, as it is
    // fatal to copy the TET_cpp pointer to another object.
    basic_TET(const basic_TET&);
    basic_TET& operator=(const basic_TET&);
};

#ifndef PDF_FEATURE_PDFTRON_TET

#if defined(TET_CPP_STRING)

typedef basic_TET<std::string, TETNoOpConverter<std::string> > TET;

#elif defined(TET_CPP_U8STRING)

// Visual Studio by default does not provide the __cplusplus macro with the
// actually supported C++ version but provides an old C++ version number
// for compatibility reasons. Therefore an extra check for
// Visual Studio 2019 version 16.1 or newer is implemented.
#if __cplusplus >= 202002L || (defined(_MSC_VER) && _MSC_VER >= 1921)
typedef basic_TET<std::u8string, TETNoOpConverter<std::u8string> > TET;
#else
#error "C++20 support required for std::u8string-based TET class"
#endif

#elif defined(TET_CPP_U16STRING)

// Visual Studio by default does not provide the __cplusplus macro with the
// actually supported C++ version but provides an old C++ version number
// for compatibility reasons. Therefore an extra check for Visual Studio 2015
// or newer is implemented.

#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1910)
typedef basic_TET<std::u16string, TETNoOpConverter<std::u16string> > TET;
#else
#error "C++11 support required for std::u16string-based TET class"
#endif

#elif defined(TET_CPP_U32STRING)

#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1910)
typedef basic_TET<std::u32string, TETNoOpConverter<std::u32string> > TET;
#else
#error "C++11 support required for std::u32string-based TET class"
#endif

#else

typedef basic_TET<std::wstring, TETNoOpConverter<std::wstring> > TET;

#endif

#else /* PDF_FEATURE_PDFTRON_TET */

#if defined(TET_CPP_STRING)

typedef basic_TET<std::string, TETNoOpConverter<std::string> > PDFTronTET;

#elif defined(TET_CPP_U8STRING)

// Visual Studio by default does not provide the __cplusplus macro with the
// actually supported C++ version but provides an old C++ version number
// for compatibility reasons. Therefore an extra check for
// Visual Studio 2019 version 16.1 or newer is implemented.
#if __cplusplus >= 202002L || (defined(_MSC_VER) && _MSC_VER >= 1921)
typedef basic_TET<std::u8string, TETNoOpConverter<std::u8string> > PDFTronTET;
#else
#error "C++20 support required for std::u8string-based TET class"
#endif

#elif defined(TET_CPP_U16STRING)

// Visual Studio by default does not provide the __cplusplus macro with the
// actually supported C++ version but provides an old C++ version number
// for compatibility reasons. Therefore an extra check for Visual Studio 2015
// or newer is implemented.

#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1910)
typedef basic_TET<std::u16string, TETNoOpConverter<std::u16string> > PDFTronTET;
#else
#error "C++11 support required for std::u16string-based TET class"
#endif

#elif defined(TET_CPP_U32STRING)

#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1910)
typedef basic_TET<std::u32string, TETNoOpConverter<std::u32string> > PDFTronTET;
#else
#error "C++11 support required for std::u32string-based TET class"
#endif

#else

typedef basic_TET<std::wstring, TETNoOpConverter<std::wstring> > PDFTronTET;

#endif


#endif /* PDF_FEATURE_PDFTRON_TET */

} // end of pdflib namespace

#endif // TETLIB_HPP
