/*
 * Simple PDF glyph dumper based on PDFlib TET
 *
 * $Id: glyphinfo.cpp,v 1.9.2.3 2010/07/29 07:40:49 rjs Exp $
 */

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>

#include "tet.hpp"

using namespace std;
using namespace pdflib;


#include <locale>

namespace
{

class UTF16Facet: public std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type>
{
   typedef std::codecvt<wchar_t,char,std::char_traits<wchar_t>::state_type> MyType;
   typedef MyType::state_type          state_type;
   typedef MyType::result              result;


    /* This function deals with converting data from the internal stream to a C/C++ file stream.*/
   virtual result do_out(state_type &state, const wchar_t *from,
            const wchar_t *from_end, const wchar_t* &from_next, char *to,
            char *to_limit, char* &to_next) const
   {
       for(; (from < from_end) && (to < to_limit); ++from, to += 2)
       {
           /* Output the Data */
	   to[0] = *from & 0xff;
	   to[1] = (*from>>8) & 0xff;
       }
       from_next = from;
       to_next   = to;

       return from < from_end ? partial : ok;
   }
};


/* global option list */
const wstring globaloptlist = L"searchpath={{../data} "
        L"{../../../resource/cmap}}";

/* document-specific option list */
const wstring docoptlist = L"";

/* page-specific option list */
const wstring pageoptlist = L"granularity=word";

wstring get_wstring(const TET& tet, const string& utf8_string);

} // end of anonymous namespace

int main(int argc, char **argv)
{
    int pageno = 0;
    try
    {
        TET tet;

        if (argc != 3)
        {
            wcerr << L"usage: glyphinfo <infilename> <outfilename>" << endl;
            return(2);
        }

        /*
         * Create an outputstream
         * and ensure that the output is written as UTF16
         */
        UTF16Facet *unicodeFacet = new UTF16Facet();
        const std::locale utf16_locale(std::cout.getloc(), unicodeFacet);
        wofstream wofp(argv[2], ios_base::binary);
        wofp.imbue(utf16_locale);

        if (!wofp)
        {
            wcerr << L"Couldn't open output file " << argv[2] << endl;
            return 2;
        }

        /* And first write a BOM */
	if (sizeof(wchar_t) == 4)
        {
	    unsigned int bom = 0xfeff;
	    wofp.write(reinterpret_cast<wchar_t*>(&bom), 1);
	}
	else
	{
	    unsigned short bom = 0xfeff;
	    wofp.write(reinterpret_cast<wchar_t*>(&bom), 1);
	}

        tet.set_option(globaloptlist);

        /*
         * Caution: For simplicity we assume that the program arguments are
         * encoded as UTF-8, which might not be true in all cases!
         */
        wstring doc_name(get_wstring(tet, string(argv[1])));
        const int doc = tet.open_document(doc_name, docoptlist);

        if (doc == -1)
        {
            wcerr << L"Error " << tet.get_errnum()
                << L" in " << tet.get_apiname() << L"(): "
                << tet.get_errmsg() << endl;
            return 2;
        }

        /* get number of pages in the document */
        const int n_pages = (int) tet.pcos_get_number(doc, L"length:pages");

        /* loop over pages in the document */
        for (pageno = 1; pageno <= n_pages; ++pageno)
        {
            wstring text;
            const int page = tet.open_page(doc, pageno, pageoptlist);

            if (page == -1)
            {
                wcerr << L"Error " << tet.get_errnum()
                    << L" in " << tet.get_apiname()
                    << L"(): " << tet.get_errmsg() << endl;
                continue;                        // try next page
            }

            // Administrative information
            wofp << endl;
            wofp << L"[ Document: '"
                        << tet.pcos_get_string(doc, L"filename")
                        << L"' ]" << endl;

            wofp << L"[ Document options: '" << docoptlist
                        << L"' ]" << endl;

            wofp << L"[ Page options: '" << pageoptlist
                        << L"' ]" << endl;

            wofp << L"[ ----- Page " << pageno
                        << L"----- ]" << endl;

            // Retrieve all text fragments
            while ((text = tet.get_text(page)) != L"")
            {
                const TET_char_info *ci;

                wofp << L"[" << text << L"]";
		wofp << endl;

                // Loop over all glyphs and print their details
                while ((ci = tet.get_char_info(page)) != NULL)
                {
                    wostringstream path;

                    // Fetch the font name with pCOS (based on its ID)
                    path << L"fonts[" << ci->fontid << "]/name";
                    wstring fontname = tet.pcos_get_string(doc, path.str());

                    // Print the character
                    wofp << L"U+";
                    wofp.setf(ios_base::hex, ios_base::basefield);
                    wofp.fill('0');
                    wofp.width(4);
                    wofp << ci->uv;
                    wofp.setf(ios_base::dec, ios_base::basefield);

                    // ...and its ASCII representation if appropriate
                    wofp << L" '" << (wchar_t)ci->uv << L"'";

                    // Print font name, size, and position
                    wofp.setf(ios_base::fixed, ios_base::floatfield);
                    wofp.precision(2);
                    wofp << " " << fontname 
                        << " size=" << ci->fontsize
                        << " x=" << ci->x
                        << " y=" << ci->y;

                    // Examine the "type" member
                    if (ci->type == TET_CT_SEQ_START)
                        wofp << " ligature_start";

                    else if (ci->type == TET_CT_SEQ_CONT)
                        wofp << " ligature_cont";

                    // Separators are only inserted for granularity > word
                    else if (ci->type == TET_CT_INSERTED)
                        wofp << " inserted";

                    /* Examine the bit flags in the "attributes" member */
                    if (ci->attributes != TET_ATTR_NONE)
                    {
                        if (ci->attributes & TET_ATTR_SUB)
                            wofp << "/sub";
                        if (ci->attributes & TET_ATTR_SUP)
                            wofp << "/sup";
                        if (ci->attributes & TET_ATTR_DROPCAP)
                            wofp << "/dropcap";
                        if (ci->attributes & TET_ATTR_SHADOW)
                            wofp << "/shadow";
                        if (ci->attributes & TET_ATTR_DEHYPHENATION_PRE)
                            wofp << "/dehyphenation_pre";
                        if (ci->attributes & TET_ATTR_DEHYPHENATION_ARTIFACT)
                            wofp << "/dehyphenation_artifact";
                        if (ci->attributes & TET_ATTR_DEHYPHENATION_POST)
                            wofp << "/dehyphenation_post";
                    }
                    wofp << endl;
                }
                wofp << endl;
            }

            if (tet.get_errnum() != 0)
            {
                wofp << L"Error " << tet.get_errnum() << L" in "
                    << tet.get_apiname() << L"(): " << tet.get_errmsg() << endl;
            }

            tet.close_page(page);
        }

        tet.close_document(doc);
    }

    catch (TET::Exception &ex) {
        if (pageno == 0)
        {
            wcerr << L"Error " << ex.get_errnum()
                << L" in " << ex.get_apiname()
                << L"(): " << ex.get_errmsg() << endl;
        }
        else
        {
            wcerr << L"Error " << ex.get_errnum()
                << L" in " << ex.get_apiname()
                << L"() on page " << pageno
                << L": " << ex.get_errmsg() << endl;
        }
        return 2;
    }

    //wofp.close();
    return 0;
}

namespace
{

/*
 * Get a wstring for the given string, assuming the string is encoded in
 * UTF-8.
 */
wstring get_wstring(const TET& tet, const string& utf8_string)
{
    const size_t size = sizeof(wstring::value_type);
    string wide_string;

    switch (size)
    {
    case 2:
        wide_string = tet.utf8_to_utf16(utf8_string, "");
        break;

    case 4:
        wide_string = tet.utf8_to_utf32(utf8_string, "");
        break;

    default:
        throw std::logic_error("Unsupported wchar_t size");
    }

    return wstring(reinterpret_cast<const wchar_t *>(wide_string.data()),
            wide_string.length() / size);
}


} // end anonymous namespace
