/*
 * SpanDSP - a series of DSP components for telephony
 *
 * t4_tx.c - ITU T.4 FAX image transmit processing
 *
 * Written by Steve Underwood <steveu@coppice.org>
 *
 * Copyright (C) 2003, 2007, 2010 Steve Underwood
 *
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*! \file */

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#include <inttypes.h>
#include <stdlib.h>
#include <limits.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <memory.h>
#include <string.h>
#if defined(HAVE_TGMATH_H)
#include <tgmath.h>
#endif
#if defined(HAVE_MATH_H)
#include <math.h>
#endif
#if defined(HAVE_STDBOOL_H)
#include <stdbool.h>
#else
#include "spandsp/stdbool.h"
#endif
#include "floating_fudge.h"
#include <tiffio.h>

#include "spandsp/telephony.h"
#include "spandsp/alloc.h"
#include "spandsp/logging.h"
#include "spandsp/bit_operations.h"
#include "spandsp/async.h"
#include "spandsp/timezone.h"
#include "spandsp/t4_rx.h"
#include "spandsp/t4_tx.h"
#include "spandsp/image_translate.h"
#include "spandsp/t81_t82_arith_coding.h"
#include "spandsp/t85.h"
#include "spandsp/t42.h"
#include "spandsp/t43.h"
#include "spandsp/t4_t6_decode.h"
#include "spandsp/t4_t6_encode.h"

#include "spandsp/private/logging.h"
#include "spandsp/private/t81_t82_arith_coding.h"
#include "spandsp/private/t85.h"
#include "spandsp/private/t42.h"
#include "spandsp/private/t43.h"
#include "spandsp/private/t4_t6_decode.h"
#include "spandsp/private/t4_t6_encode.h"
#include "spandsp/private/image_translate.h"
#include "spandsp/private/t4_rx.h"
#include "spandsp/private/t4_tx.h"

#include "faxfont.h"

#if defined(SPANDSP_SUPPORT_TIFF_FX)  &&  defined(HAVE_TIF_DIR_H)
#include <tif_dir.h>
#endif

/*! The number of centimetres in one inch */
#define CM_PER_INCH                 2.54f

typedef struct
{
    uint8_t *buf;
    int ptr;
    int row;
    int size;
    int bit_mask;
} packer_t;

static void t4_tx_set_image_type(t4_tx_state_t *s, int image_type);
static void set_image_width(t4_tx_state_t *s, uint32_t image_width);
static void set_image_length(t4_tx_state_t *s, uint32_t image_length);

static const float x_res_table[] =
{
     100.0f*100.0f/CM_PER_INCH,
     102.0f*100.0f/CM_PER_INCH,
     200.0f*100.0f/CM_PER_INCH,
     204.0f*100.0f/CM_PER_INCH,
     300.0f*100.0f/CM_PER_INCH,
     400.0f*100.0f/CM_PER_INCH,
     408.0f*100.0f/CM_PER_INCH,
     600.0f*100.0f/CM_PER_INCH,
    1200.0f*100.0f/CM_PER_INCH,
                        -1.00f
};

static const float y_res_table[] =
{
                 38.50f*100.0f,
     100.0f*100.0f/CM_PER_INCH,
                 77.00f*100.0f,
     200.0f*100.0f/CM_PER_INCH,
     300.0f*100.0f/CM_PER_INCH,
                154.00f*100.0f,
     400.0f*100.0f/CM_PER_INCH,
     600.0f*100.0f/CM_PER_INCH,
     800.0f*100.0f/CM_PER_INCH,
    1200.0f*100.0f/CM_PER_INCH,
                        -1.00f
};

static const int resolution_map[10][9] =
{
    /*  x =           100 102                    200                         204                    300                    400                          408                     600                     1200 */
    {                    0, 0,                     0,  T4_RESOLUTION_R8_STANDARD,                     0,                     0,                           0,                      0,                       0}, /* y = 3.85/mm */
    {T4_RESOLUTION_100_100, 0, T4_RESOLUTION_200_100,                          0,                     0,                     0,                           0,                      0,                       0}, /* y = 100 */
    {                    0, 0,                     0,      T4_RESOLUTION_R8_FINE,                     0,                     0,                           0,                      0,                       0}, /* y = 7.7/mm */
    {                    0, 0, T4_RESOLUTION_200_200,                          0,                     0,                     0,                           0,                      0,                       0}, /* y = 200 */
    {                    0, 0,                     0,                          0, T4_RESOLUTION_300_300,                     0,                           0,                      0,                       0}, /* y = 300 */
    {                    0, 0,                     0, T4_RESOLUTION_R8_SUPERFINE,                     0,                     0, T4_RESOLUTION_R16_SUPERFINE,                      0,                       0}, /* y = 154/mm */
    {                    0, 0, T4_RESOLUTION_200_400,                          0,                     0, T4_RESOLUTION_400_400,                           0,                      0,                       0}, /* y = 400 */
    {                    0, 0,                     0,                          0, T4_RESOLUTION_300_600,                     0,                           0,  T4_RESOLUTION_600_600,                       0}, /* y = 600 */
    {                    0, 0,                     0,                          0,                     0, T4_RESOLUTION_400_800,                           0,                      0,                       0}, /* y = 800 */
    {                    0, 0,                     0,                          0,                     0,                     0,                           0, T4_RESOLUTION_600_1200, T4_RESOLUTION_1200_1200}  /* y = 1200 */
};

#if defined(SPANDSP_SUPPORT_TIFF_FX)
/* TIFF-FX related extensions to the tag set supported by libtiff */

static const TIFFFieldInfo tiff_fx_tiff_field_info[] =
{
    {TIFFTAG_INDEXED, 1, 1, TIFF_SHORT, FIELD_CUSTOM, false, false, (char *) "Indexed"},
    {TIFFTAG_GLOBALPARAMETERSIFD, 1, 1, TIFF_IFD8, FIELD_CUSTOM, false, false, (char *) "GlobalParametersIFD"},
    {TIFFTAG_PROFILETYPE, 1, 1, TIFF_LONG, FIELD_CUSTOM, false, false, (char *) "ProfileType"},
    {TIFFTAG_FAXPROFILE, 1, 1, TIFF_BYTE, FIELD_CUSTOM, false, false, (char *) "FaxProfile"},
    {TIFFTAG_CODINGMETHODS, 1, 1, TIFF_LONG, FIELD_CUSTOM, false, false, (char *) "CodingMethods"},
    {TIFFTAG_VERSIONYEAR, 4, 4, TIFF_BYTE, FIELD_CUSTOM, false, false, (char *) "VersionYear"},
    {TIFFTAG_MODENUMBER, 1, 1, TIFF_BYTE, FIELD_CUSTOM, false, false, (char *) "ModeNumber"},
    {TIFFTAG_DECODE, TIFF_VARIABLE, TIFF_VARIABLE, TIFF_SRATIONAL, FIELD_CUSTOM, false, true, (char *) "Decode"},
    {TIFFTAG_IMAGEBASECOLOR, TIFF_VARIABLE, TIFF_VARIABLE, TIFF_SHORT, FIELD_CUSTOM, false, true, (char *) "ImageBaseColor"},
    {TIFFTAG_T82OPTIONS, 1, 1, TIFF_LONG, FIELD_CUSTOM, false, false, (char *) "T82Options"},
    {TIFFTAG_STRIPROWCOUNTS, TIFF_VARIABLE, TIFF_VARIABLE, TIFF_LONG, FIELD_CUSTOM, false, true, (char *) "StripRowCounts"},
    {TIFFTAG_IMAGELAYER, 2, 2, TIFF_LONG, FIELD_CUSTOM, false, false, (char *) "ImageLayer"},
};

#if TIFFLIB_VERSION >= 20120922  &&  defined(HAVE_TIF_DIR_H)
static TIFFField tiff_fx_tiff_fields[] =
{
    { TIFFTAG_INDEXED, 1, 1, TIFF_SHORT, 0, TIFF_SETGET_UINT16, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "Indexed" },
    { TIFFTAG_GLOBALPARAMETERSIFD, 1, 1, TIFF_IFD8, 0, TIFF_SETGET_IFD8, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 0, 0, (char *) "GlobalParametersIFD", NULL },
    { TIFFTAG_PROFILETYPE, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "ProfileType", NULL },
    { TIFFTAG_FAXPROFILE, 1, 1, TIFF_BYTE, 0, TIFF_SETGET_UINT8, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "FaxProfile", NULL },
    { TIFFTAG_CODINGMETHODS, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "CodingMethods", NULL },
    { TIFFTAG_VERSIONYEAR, 4, 4, TIFF_BYTE, 0, TIFF_SETGET_C0_UINT8, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "VersionYear", NULL },
    { TIFFTAG_MODENUMBER, 1, 1, TIFF_BYTE, 0, TIFF_SETGET_UINT8, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "ModeNumber", NULL },
    { TIFFTAG_DECODE, -1, -1, TIFF_SRATIONAL, 0, TIFF_SETGET_C16_FLOAT, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 1, (char *) "Decode", NULL },
    { TIFFTAG_IMAGEBASECOLOR, -1, -1, TIFF_SHORT, 0, TIFF_SETGET_C16_UINT16, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 1, (char *) "ImageBaseColor", NULL },
    { TIFFTAG_T82OPTIONS, 1, 1, TIFF_LONG, 0, TIFF_SETGET_UINT32, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "T82Options", NULL },
    { TIFFTAG_STRIPROWCOUNTS, -1, -1, TIFF_LONG, 0, TIFF_SETGET_C16_UINT32, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 1, (char *) "StripRowCounts", NULL },
    { TIFFTAG_IMAGELAYER, 2, 2, TIFF_LONG, 0, TIFF_SETGET_C0_UINT32, TIFF_SETGET_UNDEFINED, FIELD_CUSTOM, 1, 0, (char *) "ImageLayer", NULL },
};

TIFFFieldArray tiff_fx_field_array = { tfiatOther, 0, 12, tiff_fx_tiff_fields };
#endif

static TIFFExtendProc _ParentExtender = NULL;

static void TIFFFXDefaultDirectory(TIFF *tif)
{
    /* Install the extended tag field info */
    TIFFMergeFieldInfo(tif, tiff_fx_tiff_field_info, 12);

    /* Since we may have overriddden another directory method, we call it now to
       allow it to set up the rest of its own methods. */
    if (_ParentExtender)
        (*_ParentExtender)(tif);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) TIFF_FX_init(void)
{
    static int first_time = true;

    if (!first_time)
        return;
    first_time = false;

    /* Grab the inherited method and install */
    _ParentExtender = TIFFSetTagExtender(TIFFFXDefaultDirectory);
}
/*- End of function --------------------------------------------------------*/
#endif

static int code_to_x_resolution(int code)
{
    static const int xxx[] =
    {
        T4_X_RESOLUTION_R8,         /* R8 x standard */
        T4_X_RESOLUTION_R8,         /* R8 x fine */
        T4_X_RESOLUTION_R8,         /* R8 x superfine */
        T4_X_RESOLUTION_R16,        /* R16 x superfine */
        T4_X_RESOLUTION_100,        /* 100x100 */
        T4_X_RESOLUTION_200,        /* 200x100 */
        T4_X_RESOLUTION_200,        /* 200x200 */
        T4_X_RESOLUTION_200,        /* 200x400 */
        T4_X_RESOLUTION_300,        /* 300x300 */
        T4_X_RESOLUTION_300,        /* 300x600 */
        T4_X_RESOLUTION_400,        /* 400x400 */
        T4_X_RESOLUTION_400,        /* 400x800 */
        T4_X_RESOLUTION_600,        /* 600x600 */
        T4_X_RESOLUTION_600,        /* 600x1200 */
        T4_X_RESOLUTION_1200        /* 1200x1200 */
    };
    int entry;

    entry = top_bit(code);
    if (entry < 0  ||  entry > 14)
        return 0;
    return xxx[entry];
}
/*- End of function --------------------------------------------------------*/

static int code_to_y_resolution(int code)
{
    static const int yyy[] =
    {
        T4_Y_RESOLUTION_STANDARD,   /* R8 x standard */
        T4_Y_RESOLUTION_FINE,       /* R8 x fine */
        T4_Y_RESOLUTION_SUPERFINE,  /* R8 x superfine */
        T4_Y_RESOLUTION_SUPERFINE,  /* R16 x superfine */
        T4_Y_RESOLUTION_100,        /* 100x100 */
        T4_Y_RESOLUTION_100,        /* 200x100 */
        T4_Y_RESOLUTION_200,        /* 200x200 */
        T4_Y_RESOLUTION_400,        /* 200x400 */
        T4_Y_RESOLUTION_300,        /* 300x300 */
        T4_Y_RESOLUTION_600,        /* 300x600 */
        T4_Y_RESOLUTION_400,        /* 400x400 */
        T4_Y_RESOLUTION_800,        /* 400x800 */
        T4_Y_RESOLUTION_600,        /* 600x600 */
        T4_Y_RESOLUTION_1200,       /* 600x1200 */
        T4_Y_RESOLUTION_1200        /* 1200x1200 */
    };
    int entry;

    entry = top_bit(code);
    if (entry < 0  ||  entry > 14)
        return 0;
    return yyy[entry];
}
/*- End of function --------------------------------------------------------*/

static int match_resolution(float actual, const float table[])
{
    int i;
    int best_entry;
    float best_ratio;
    float ratio;

    if (actual == 0.0f)
        return -1;

    best_ratio = 0.0f;
    best_entry = -1;
    for (i = 0;  table[i] > 0.0f;  i++)
    {
        if (actual > table[i])
            ratio = table[i]/actual;
        else
            ratio = actual/table[i];
        if (ratio > best_ratio)
        {
            best_entry = i;
            best_ratio = ratio;
        }
    }
    if (best_ratio < 0.95f)
        return -1;
    return best_entry;
}
/*- End of function --------------------------------------------------------*/

#if 0
static int best_colour_resolution(float actual, int allowed_resolutions)
{
    static const struct
    {
        float resolution;
        int resolution_code;
    } x_res_table[] =
    {
        { 100.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_100_100},
        { 200.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_200_200},
        { 300.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_300_300},
        { 400.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_400_400},
        { 600.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_600_600},
        {1200.0f*100.0f/CM_PER_INCH, T4_RESOLUTION_1200_1200},
        {                    -1.00f, -1}
    };
    int i;
    int best_entry;
    float best_ratio;
    float ratio;

    if (actual == 0.0f)
        return -1;

    best_ratio = 0.0f;
    best_entry = 0;
    for (i = 0;  x_res_table[i].resolution > 0.0f;  i++)
    {
        if (!(allowed_resolutions & x_res_table[i].resolution_code))
            continue;
        if (actual > x_res_table[i].resolution)
            ratio = x_res_table[i].resolution/actual;
        else
            ratio = actual/x_res_table[i].resolution;
        if (ratio > best_ratio)
        {
            best_entry = i;
            best_ratio = ratio;
        }
    }
    return x_res_table[best_entry].resolution_code;
}
/*- End of function --------------------------------------------------------*/
#endif

#if defined(SPANDSP_SUPPORT_TIFF_FX)
static int read_colour_map(t4_tx_state_t *s, int bits_per_sample)
{
    int i;
    uint16_t *map_L;
    uint16_t *map_a;
    uint16_t *map_b;
    uint16_t *map_z;

    map_L = NULL;
    map_a = NULL;
    map_b = NULL;
    map_z = NULL;
    if (!TIFFGetField(s->tiff.tiff_file, TIFFTAG_COLORMAP, &map_L, &map_a, &map_b, &map_z))
        return -1;

    /* TODO: This only allows for 8 bit deep maps */
    span_log(&s->logging, SPAN_LOG_FLOW, "Got a colour map\n");
    s->colour_map_entries = 1 << bits_per_sample;
    if ((s->colour_map = span_realloc(s->colour_map, 3*s->colour_map_entries)) == NULL)
        return -1;
#if 0
    /* Sweep the colormap in the proper order */
    for (i = 0;  i < s->colour_map_entries;  i++)
    {
        s->colour_map[3*i + 0] = (map_L[i] >> 8) & 0xFF;
        s->colour_map[3*i + 1] = (map_a[i] >> 8) & 0xFF;
        s->colour_map[3*i + 2] = (map_b[i] >> 8) & 0xFF;
        span_log(&s->logging, SPAN_LOG_FLOW, "Map %3d - %5d %5d %5d\n", i, s->colour_map[3*i], s->colour_map[3*i + 1], s->colour_map[3*i + 2]);
    }
#else
    /* Sweep the colormap in the order that seems to work for l04x_02x.tif */
    for (i = 0;  i < s->colour_map_entries;  i++)
    {
        s->colour_map[0*s->colour_map_entries + i] = (map_L[i] >> 8) & 0xFF;
        s->colour_map[1*s->colour_map_entries + i] = (map_a[i] >> 8) & 0xFF;
        s->colour_map[2*s->colour_map_entries + i] = (map_b[i] >> 8) & 0xFF;
    }
#endif
    lab_to_srgb(&s->lab_params, s->colour_map, s->colour_map, s->colour_map_entries);
    for (i = 0;  i < s->colour_map_entries;  i++)
        span_log(&s->logging, SPAN_LOG_FLOW, "Map %3d - %5d %5d %5d\n", i, s->colour_map[3*i], s->colour_map[3*i + 1], s->colour_map[3*i + 2]);
    return 0;
}
/*- End of function --------------------------------------------------------*/
#endif

static int get_tiff_directory_info(t4_tx_state_t *s)
{
#if defined(SPANDSP_SUPPORT_TIFF_FX)
    static const char *tiff_fx_fax_profiles[] =
    {
        "???",
        "profile S",
        "profile F",
        "profile J",
        "profile C",
        "profile L",
        "profile M"
    };
    char *u;
    char uu[10];
    float *fl_parms;
    toff_t diroff;
    float lmin;
    float lmax;
    float amin;
    float amax;
    float bmin;
    float bmax;
    uint8_t parm8;
#endif
#if defined(SPANDSP_SUPPORT_TIFF_FX)
    uint16_t parm16;
#endif
    uint32_t parm32;
    int best_x_entry;
    int best_y_entry;
    float x_resolution;
    float y_resolution;
    t4_tx_tiff_state_t *t;
    uint16_t bits_per_sample;
    uint16_t samples_per_pixel;
    uint16_t res_unit;
    uint16_t YCbCrSubsample_horiz;
    uint16_t YCbCrSubsample_vert;

    t = &s->tiff;
    bits_per_sample = 1;
    TIFFGetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
    samples_per_pixel = 1;
    TIFFGetField(t->tiff_file, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
    if (samples_per_pixel == 1  &&  bits_per_sample == 1)
        t->image_type = T4_IMAGE_TYPE_BILEVEL;
    else if (samples_per_pixel == 3  &&  bits_per_sample == 1)
        t->image_type = T4_IMAGE_TYPE_COLOUR_BILEVEL;
    else if (samples_per_pixel == 4  &&  bits_per_sample == 1)
        t->image_type = T4_IMAGE_TYPE_COLOUR_BILEVEL;
    else if (samples_per_pixel == 1  &&  bits_per_sample == 8)
        t->image_type = T4_IMAGE_TYPE_GRAY_8BIT;
    else if (samples_per_pixel == 1  &&  bits_per_sample > 8)
        t->image_type = T4_IMAGE_TYPE_GRAY_12BIT;
    else if (samples_per_pixel == 3  &&  bits_per_sample == 8)
        t->image_type = T4_IMAGE_TYPE_COLOUR_8BIT;
    else if (samples_per_pixel == 3  &&  bits_per_sample > 8)
        t->image_type = T4_IMAGE_TYPE_COLOUR_12BIT;
    else
        return -1;

#if defined(SPANDSP_SUPPORT_TIFF_FX)
    parm16 = 0;
    if (TIFFGetField(t->tiff_file, TIFFTAG_INDEXED, &parm16))
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "Indexed %s (%u)\n", (parm16)  ?  "palette image"  :  "non-palette image", parm16);
        if (parm16 == 1)
        {
            /* Its an indexed image, so its really a colour image, even though it may have only one sample per pixel */
            if (samples_per_pixel == 1  &&  bits_per_sample == 8)
                t->image_type = T4_IMAGE_TYPE_COLOUR_8BIT;
            else if (samples_per_pixel == 1  &&  bits_per_sample > 8)
                t->image_type = T4_IMAGE_TYPE_COLOUR_12BIT;
        }
    }
#endif

    parm32 = 0;
    TIFFGetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, &parm32);
    t->image_width = parm32;
    parm32 = 0;
    TIFFGetField(t->tiff_file, TIFFTAG_IMAGELENGTH, &parm32);
    t->image_length = parm32;

    x_resolution = 0.0f;
    TIFFGetField(t->tiff_file, TIFFTAG_XRESOLUTION, &x_resolution);
    y_resolution = 0.0f;
    TIFFGetField(t->tiff_file, TIFFTAG_YRESOLUTION, &y_resolution);
    res_unit = RESUNIT_INCH;
    TIFFGetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, &res_unit);

    t->x_resolution = x_resolution*100.0f;
    t->y_resolution = y_resolution*100.0f;
    if (res_unit == RESUNIT_INCH)
    {
        t->x_resolution /= CM_PER_INCH;
        t->y_resolution /= CM_PER_INCH;
    }

    if (((best_x_entry = match_resolution(t->x_resolution, x_res_table)) >= 0)
        &&
        ((best_y_entry = match_resolution(t->y_resolution, y_res_table)) >= 0))
    {
        t->resolution_code = resolution_map[best_y_entry][best_x_entry];
    }
    else
    {
        t->resolution_code = 0;
    }

    t->photo_metric = PHOTOMETRIC_MINISWHITE;
    TIFFGetField(t->tiff_file, TIFFTAG_PHOTOMETRIC, &t->photo_metric);

    /* The default luminant is D50 */
    set_lab_illuminant(&s->lab_params, 96.422f, 100.000f,  82.521f);
    set_lab_gamut(&s->lab_params, 0, 100, -85, 85, -75, 125, false);

    t->compression = -1;
    TIFFGetField(t->tiff_file, TIFFTAG_COMPRESSION, &t->compression);
    switch (t->compression)
    {
    case COMPRESSION_CCITT_T4:
        span_log(&s->logging, SPAN_LOG_FLOW, "T.4\n");
        break;
    case COMPRESSION_CCITT_T6:
        span_log(&s->logging, SPAN_LOG_FLOW, "T.6\n");
        break;
    case COMPRESSION_T85:
        span_log(&s->logging, SPAN_LOG_FLOW, "T.85\n");
        break;
    case COMPRESSION_T43:
        span_log(&s->logging, SPAN_LOG_FLOW, "T.43\n");
        break;
    case COMPRESSION_JPEG:
        span_log(&s->logging, SPAN_LOG_FLOW, "JPEG\n");
        if (t->photo_metric == PHOTOMETRIC_ITULAB)
            span_log(&s->logging, SPAN_LOG_FLOW, "ITULAB\n");
        break;
    case COMPRESSION_NONE:
        span_log(&s->logging, SPAN_LOG_FLOW, "No compression\n");
        break;
    default:
        span_log(&s->logging, SPAN_LOG_FLOW, "Unexpected compression %d\n", t->compression);
        break;
    }

#if defined(SPANDSP_SUPPORT_TIFF_FX)
    read_colour_map(s, bits_per_sample);
#endif

    YCbCrSubsample_horiz = 0;
    YCbCrSubsample_vert = 0;
    if (TIFFGetField(t->tiff_file, TIFFTAG_YCBCRSUBSAMPLING, &YCbCrSubsample_horiz, &YCbCrSubsample_vert))
        span_log(&s->logging, SPAN_LOG_FLOW, "Subsampling %d %d\n", YCbCrSubsample_horiz, YCbCrSubsample_vert);

    t->fill_order = FILLORDER_LSB2MSB;

#if defined(SPANDSP_SUPPORT_TIFF_FX)
    if (TIFFGetField(t->tiff_file, TIFFTAG_PROFILETYPE, &parm32))
        span_log(&s->logging, SPAN_LOG_FLOW, "Profile type %u\n", parm32);
    if (TIFFGetField(t->tiff_file, TIFFTAG_FAXPROFILE, &parm8))
        span_log(&s->logging, SPAN_LOG_FLOW, "FAX profile %s (%u)\n", tiff_fx_fax_profiles[parm8], parm8);

    if (TIFFGetField(t->tiff_file, TIFFTAG_CODINGMETHODS, &parm32))
        span_log(&s->logging, SPAN_LOG_FLOW, "Coding methods 0x%x\n", parm32);
    if (TIFFGetField(t->tiff_file, TIFFTAG_VERSIONYEAR, &u))
    {
        memcpy(uu, u, 4);
        uu[4] = '\0';
        span_log(&s->logging, SPAN_LOG_FLOW, "Version year \"%s\"\n", uu);
    }
    if (TIFFGetField(t->tiff_file, TIFFTAG_MODENUMBER, &parm8))
        span_log(&s->logging, SPAN_LOG_FLOW, "Mode number %u\n", parm8);

    switch (t->photo_metric)
    {
    case PHOTOMETRIC_ITULAB:
#if 1
        /* 8 bit version */
        lmin = 0.0f;
        lmax = 100.0f;
        amin = -21760.0f/255.0f;
        amax = 21590.0f/255.0f;
        bmin = -19200.0f/255.0f;
        bmax = 31800.0f/255.0f;
#else
        /* 12 bit version */
        lmin = 0.0f;
        lmax = 100.0f;
        amin = -348160.0f/4095.0f
        amax = 347990.0f/4095.0f
        bmin = -307200.0f/4095.0f
        bmax = 511800.0f/4095.0f
#endif
        break;
    default:
        lmin = 0.0f;
        lmax = 0.0f;
        amin = 0.0f;
        amax = 0.0f;
        bmin = 0.0f;
        bmax = 0.0f;
        break;
    }

    if (TIFFGetField(t->tiff_file, TIFFTAG_DECODE, &parm16, &fl_parms))
    {
        lmin = fl_parms[0];
        lmax = fl_parms[1];
        amin = fl_parms[2];
        amax = fl_parms[3];
        bmin = fl_parms[4];
        bmax = fl_parms[5];
        span_log(&s->logging, SPAN_LOG_FLOW, "Got decode tag %f %f %f %f %f %f\n", lmin, lmax, amin, amax, bmin, bmax);
    }

    /* TIFFTAG_IMAGEBASECOLOR */

    if (TIFFGetField(t->tiff_file, TIFFTAG_T82OPTIONS, &parm32))
        span_log(&s->logging, SPAN_LOG_FLOW, "T.82 options 0x%x\n", parm32);

    /* TIFFTAG_STRIPROWCOUNTS */
    /* TIFFTAG_IMAGELAYER */

    /* If global parameters are present they should only be on the first page of the file.
       However, as we scan the file we might as well look for them on any page. */
    diroff = 0;
    if (TIFFGetField(t->tiff_file, TIFFTAG_GLOBALPARAMETERSIFD, &diroff))
    {
#if TIFFLIB_VERSION >= 20120922  &&  defined(HAVE_TIF_DIR_H)
        if (!TIFFReadCustomDirectory(t->tiff_file, diroff, &tiff_fx_field_array))
        {
            span_log(&s->logging, SPAN_LOG_FLOW, "Global parameter read failed\n");
        }
        else
        {
            span_log(&s->logging, SPAN_LOG_FLOW, "Global parameters\n");
            if (TIFFGetField(t->tiff_file, TIFFTAG_PROFILETYPE, &parm32))
                span_log(&s->logging, SPAN_LOG_FLOW, "  Profile type %u\n", parm32);
            if (TIFFGetField(t->tiff_file, TIFFTAG_FAXPROFILE, &parm8))
                span_log(&s->logging, SPAN_LOG_FLOW, "  FAX profile %s (%u)\n", tiff_fx_fax_profiles[parm8], parm8);
            if (TIFFGetField(t->tiff_file, TIFFTAG_CODINGMETHODS, &parm32))
                span_log(&s->logging, SPAN_LOG_FLOW, "  Coding methods 0x%x\n", parm32);
            if (TIFFGetField(t->tiff_file, TIFFTAG_VERSIONYEAR, &u))
            {
                memcpy(uu, u, 4);
                uu[4] = '\0';
                span_log(&s->logging, SPAN_LOG_FLOW, "  Version year \"%s\"\n", uu);
            }
            if (TIFFGetField(t->tiff_file, TIFFTAG_MODENUMBER, &parm8))
                span_log(&s->logging, SPAN_LOG_FLOW, "  Mode number %u\n", parm8);

            if (!TIFFSetDirectory(t->tiff_file, (tdir_t) s->current_page))
                span_log(&s->logging, SPAN_LOG_FLOW, "Failed to set directory to page %d\n", s->current_page);
        }
#endif
    }
#endif
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int test_tiff_directory_info(t4_tx_state_t *s)
{
    uint16_t res_unit;
    uint32_t parm32;
    uint16_t bits_per_sample;
    uint16_t samples_per_pixel;
    int image_type;
    float x_resolution;
    float y_resolution;
    t4_tx_tiff_state_t *t;

    t = &s->tiff;
    bits_per_sample = 1;
    TIFFGetField(t->tiff_file, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
    samples_per_pixel = 1;
    TIFFGetField(t->tiff_file, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
    if (samples_per_pixel == 1  &&  bits_per_sample == 1)
        image_type = T4_IMAGE_TYPE_BILEVEL;
    else if (samples_per_pixel == 3  &&  bits_per_sample == 1)
        image_type = T4_IMAGE_TYPE_COLOUR_BILEVEL;
    else if (samples_per_pixel == 4  &&  bits_per_sample == 1)
        image_type = T4_IMAGE_TYPE_COLOUR_BILEVEL;
    else if (samples_per_pixel == 1  &&  bits_per_sample == 8)
        image_type = T4_IMAGE_TYPE_GRAY_8BIT;
    else if (samples_per_pixel == 1  &&  bits_per_sample > 8)
        image_type = T4_IMAGE_TYPE_GRAY_12BIT;
    else if (samples_per_pixel == 3  &&  bits_per_sample == 8)
        image_type = T4_IMAGE_TYPE_COLOUR_8BIT;
    else if (samples_per_pixel == 3  &&  bits_per_sample > 8)
        image_type = T4_IMAGE_TYPE_COLOUR_12BIT;
    else
        image_type = -1;
    if (t->image_type != image_type)
        return 1;

    parm32 = 0;
    TIFFGetField(t->tiff_file, TIFFTAG_IMAGEWIDTH, &parm32);
    if (s->tiff.image_width != (int) parm32)
        return 2;

    x_resolution = 0.0f;
    TIFFGetField(t->tiff_file, TIFFTAG_XRESOLUTION, &x_resolution);
    y_resolution = 0.0f;
    TIFFGetField(t->tiff_file, TIFFTAG_YRESOLUTION, &y_resolution);
    res_unit = RESUNIT_INCH;
    TIFFGetField(t->tiff_file, TIFFTAG_RESOLUTIONUNIT, &res_unit);

    x_resolution *= 100.0f;
    y_resolution *= 100.0f;
    if (res_unit == RESUNIT_INCH)
    {
        x_resolution /= CM_PER_INCH;
        y_resolution /= CM_PER_INCH;
    }
    if (s->tiff.x_resolution != (int) x_resolution)
        return 3;
    if (s->tiff.y_resolution != (int) y_resolution)
        return 4;

    return 0;
}
/*- End of function --------------------------------------------------------*/

static int get_tiff_total_pages(t4_tx_state_t *s)
{
    int max;

    /* Each page *should* contain the total number of pages, but can this be
       trusted? Some files say 0. Actually searching for the last page is
       more reliable. */
    max = 0;
    while (TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) max))
        max++;
    /* Back to the previous page */
    if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page))
        return -1;
    return max;
}
/*- End of function --------------------------------------------------------*/

static int open_tiff_input_file(t4_tx_state_t *s, const char *file)
{
    if ((s->tiff.tiff_file = TIFFOpen(file, "r")) == NULL)
        return -1;
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int metadata_row_read_handler(void *user_data, uint8_t buf[], size_t len)
{
    t4_tx_state_t *s;

    s = (t4_tx_state_t *) user_data;
    if (s->tiff.row >= s->metadata.image_length)
        return 0;
    memcpy(buf, &s->tiff.image_buffer[s->tiff.row*len], len);
    s->tiff.row++;
    return len;
}
/*- End of function --------------------------------------------------------*/

static int tiff_row_read_handler(void *user_data, uint8_t buf[], size_t len)
{
    t4_tx_state_t *s;
    int i;
    int j;

    s = (t4_tx_state_t *) user_data;
    if (s->tiff.row >= s->tiff.image_length)
        return 0;
    if (s->tiff.image_buffer == NULL)
        return 0;
    memcpy(buf, &s->tiff.image_buffer[s->tiff.row*len], len);
    s->tiff.row++;

    /* If this is a bi-level image which has more vertical resolution than the
       far end will accept, we need to squash it down to size. */
    for (i = 1;  i < s->row_squashing_ratio  &&  s->tiff.row < s->tiff.image_length;  i++)
    {
        for (j = 0;  j < len;  j++)
            buf[j] |= s->tiff.image_buffer[s->tiff.row*len + j];
        s->tiff.row++;
    }
    return len;
}
/*- End of function --------------------------------------------------------*/

static int translate_row_read2(void *user_data, uint8_t buf[], size_t len)
{
    t4_tx_state_t *s;

    s = (t4_tx_state_t *) user_data;
    memcpy(buf, &s->pack_buf[s->pack_ptr], len);
    s->pack_ptr += len;
    s->pack_row++;
    return len;
}
/*- End of function --------------------------------------------------------*/

static int translate_row_read(void *user_data, uint8_t buf[], size_t len)
{
    t4_tx_state_t *s;
    int i;
    int j;

    s = (t4_tx_state_t *) user_data;

    if (s->tiff.raw_row >= s->tiff.image_length)
        return 0;

    if (TIFFReadScanline(s->tiff.tiff_file, buf, s->tiff.raw_row, 0) < 0)
        return 0;
    s->tiff.raw_row++;

    /* If this is a bi-level image which is stretched more vertically than we are able
       to send we need to squash it down to size. */
    for (i = 1;  i < s->row_squashing_ratio;  i++)
    {
#if defined(_MSC_VER)
        uint8_t *extra_buf = (uint8_t *) _alloca(len);
#else
        uint8_t extra_buf[len];
#endif

        if (TIFFReadScanline(s->tiff.tiff_file, extra_buf, s->tiff.raw_row, 0) < 0)
            return 0;
        s->tiff.raw_row++;
        /* We know this is a bi-level image if we are squashing */
        for (j = 0;  j < s->tiff.image_width/8;  j++)
            buf[j] |= extra_buf[s->tiff.image_width/8 + j];
    }
    if (s->apply_lab)
        lab_to_srgb(&s->lab_params, buf, buf, len/3);
    return len;
}
/*- End of function --------------------------------------------------------*/

static int packing_row_write_handler(void *user_data, const uint8_t buf[], size_t len)
{
    packer_t *s;

    s = (packer_t *) user_data;
    memcpy(&s->buf[s->ptr], buf, len);
    s->ptr += len;
    s->row++;
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int embedded_comment_handler(void *user_data, const uint8_t buf[], size_t len)
{
    t4_tx_state_t *s;

    s = (t4_tx_state_t *) user_data;
    if (buf)
        span_log(&s->logging, SPAN_LOG_WARNING, "T.85 comment (%d): %s\n", (int) len, buf);
    else
        span_log(&s->logging, SPAN_LOG_WARNING, "T.85 comment (%d): ---\n", (int) len);
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int read_tiff_raw_image(t4_tx_state_t *s)
{
    int num_strips;
    int total_len;
    int len;
    int i;

    num_strips = TIFFNumberOfStrips(s->tiff.tiff_file);
    total_len = 0;
    for (i = 0;  i < num_strips;  i++)
        total_len += TIFFRawStripSize(s->tiff.tiff_file, i);
    if ((s->no_encoder.buf = span_realloc(s->no_encoder.buf, total_len)) == NULL)
        return -1;
    total_len = 0;
    for (i = 0;  i < num_strips;  i++, total_len += len)
    {
        len = TIFFRawStripSize(s->tiff.tiff_file, i);
        if ((len = TIFFReadRawStrip(s->tiff.tiff_file, i, &s->no_encoder.buf[total_len], len)) < 0)
        {
            span_log(&s->logging, SPAN_LOG_WARNING, "%s: TIFFReadRawStrip error.\n", s->tiff.file);
            return -1;
        }
    }
    s->no_encoder.buf_len = total_len;
    s->no_encoder.buf_ptr = 0;
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int read_tiff_t85_image(t4_tx_state_t *s)
{
    int biggest;
    int num_strips;
    int len;
    int i;
    int result;
    uint8_t *t;
    uint8_t *raw_data;
    t85_decode_state_t t85;
    packer_t pack;

    /* Size up and allocate the buffer for the raw data */
    num_strips = TIFFNumberOfStrips(s->tiff.tiff_file);
    biggest = 0;
    for (i = 0;  i < num_strips;  i++)
    {
        len = TIFFRawStripSize(s->tiff.tiff_file, i);
        if (len > biggest)
            biggest = len;
    }
    if ((raw_data = span_alloc(biggest)) == NULL)
        return -1;

    s->tiff.image_size = s->tiff.image_length*((s->tiff.image_width + 7)/8);
    if (s->tiff.image_size >= s->tiff.image_buffer_size)
    {
        if ((t = span_realloc(s->tiff.image_buffer, s->tiff.image_size)) == NULL)
        {
            span_free(raw_data);
            return -1;
        }
        s->tiff.image_buffer_size = s->tiff.image_size;
        s->tiff.image_buffer = t;
    }

    pack.buf = s->tiff.image_buffer;
    pack.ptr = 0;
    pack.size = s->tiff.image_size;
    pack.row = 0;
    t85_decode_init(&t85, packing_row_write_handler, &pack);
    t85_decode_set_comment_handler(&t85, 1000, embedded_comment_handler, s);
    t85_decode_set_image_size_constraints(&t85, s->tiff.image_width, s->tiff.image_length);
    result = -1;
    for (i = 0;  i < num_strips;  i++)
    {
        len = TIFFRawStripSize(s->tiff.tiff_file, i);
        if ((len = TIFFReadRawStrip(s->tiff.tiff_file, i, raw_data, len)) < 0)
        {
            span_log(&s->logging, SPAN_LOG_WARNING, "%s: TIFFReadRawStrip error.\n", s->tiff.file);
            span_free(raw_data);
            return -1;
        }
        if ((result = t85_decode_put(&t85, raw_data, len)) != T4_DECODE_MORE_DATA)
            break;
    }
    if (result == T4_DECODE_MORE_DATA)
        t85_decode_put(&t85, NULL, 0);

    len = t85_decode_get_compressed_image_size(&t85);
    span_log(&s->logging, SPAN_LOG_WARNING, "Compressed image is %d bytes, %d rows\n", len/8, s->tiff.image_length);
    t85_decode_release(&t85);
    span_free(raw_data);
    return 0;
}
/*- End of function --------------------------------------------------------*/

#if defined(SPANDSP_SUPPORT_T43)
static int read_tiff_t43_image(t4_tx_state_t *s)
{
    int biggest;
    int num_strips;
    int len;
    int i;
    int result;
    uint8_t *t;
    uint8_t *raw_data;
    logging_state_t *logging;
    t43_decode_state_t t43;
    packer_t pack;
    uint16_t bits_per_sample;
    uint16_t samples_per_pixel;

    bits_per_sample = 1;
    TIFFGetField(s->tiff.tiff_file, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
    samples_per_pixel = 3;
    TIFFGetField(s->tiff.tiff_file, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);

    samples_per_pixel = 3;

    num_strips = TIFFNumberOfStrips(s->tiff.tiff_file);
    biggest = 0;
    for (i = 0;  i < num_strips;  i++)
    {
        len = TIFFRawStripSize(s->tiff.tiff_file, i);
        if (len > biggest)
            biggest = len;
    }
    if ((raw_data = span_alloc(biggest)) == NULL)
        return -1;

    s->tiff.image_size = samples_per_pixel*s->tiff.image_width*s->tiff.image_length;
    if (s->tiff.image_size >= s->tiff.image_buffer_size)
    {
        if ((t = span_realloc(s->tiff.image_buffer, s->tiff.image_size)) == NULL)
        {
            span_free(raw_data);
            return -1;
        }
        s->tiff.image_buffer_size = s->tiff.image_size;
        s->tiff.image_buffer = t;
    }

    t43_decode_init(&t43, packing_row_write_handler, &pack);
    t43_decode_set_comment_handler(&t43, 1000, embedded_comment_handler, NULL);
    t43_decode_set_image_size_constraints(&t43, s->tiff.image_width, s->tiff.image_length);
    logging = t43_decode_get_logging_state(&t43);
    span_log_set_level(logging, SPAN_LOG_SHOW_SEVERITY | SPAN_LOG_SHOW_PROTOCOL | SPAN_LOG_FLOW);

    pack.buf = s->tiff.image_buffer;
    pack.ptr = 0;
    pack.size = s->tiff.image_size;
    pack.row = 0;

    result = -1;
    for (i = 0;  i < num_strips;  i++)
    {
        len = TIFFRawStripSize(s->tiff.tiff_file, i);
        if ((len = TIFFReadRawStrip(s->tiff.tiff_file, i, raw_data, len)) < 0)
        {
            span_log(&s->logging, SPAN_LOG_WARNING, "%s: TIFFReadRawStrip error.\n", s->tiff.file);
            span_free(raw_data);
            return -1;
        }
        if ((result = t43_decode_put(&t43, raw_data, len)) != T4_DECODE_MORE_DATA)
            break;
    }
    if (result == T4_DECODE_MORE_DATA)
        result = t43_decode_put(&t43, NULL, 0);

    t43_decode_release(&t43);
    span_free(raw_data);
    return s->tiff.image_size;
}
/*- End of function --------------------------------------------------------*/
#endif

static int read_tiff_t42_t81_image(t4_tx_state_t *s)
{
    int total_len;
    int len;
    int i;
    int num_strips;
    int total_image_len;
    uint8_t *t;
    uint8_t *raw_data;
    uint8_t *jpeg_table;
    uint32_t jpeg_table_len;
    packer_t pack;
    uint16_t bits_per_sample;
    uint16_t samples_per_pixel;
    t42_decode_state_t t42;

    bits_per_sample = 1;
    TIFFGetField(s->tiff.tiff_file, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
    samples_per_pixel = 1;
    TIFFGetField(s->tiff.tiff_file, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);

    num_strips = TIFFNumberOfStrips(s->tiff.tiff_file);
    total_image_len = 0;
    jpeg_table_len = 0;
    if (TIFFGetField(s->tiff.tiff_file, TIFFTAG_JPEGTABLES, &jpeg_table_len, &jpeg_table))
    {
        total_image_len += (jpeg_table_len - 4);
        span_log(&s->logging, SPAN_LOG_FLOW, "JPEG tables %u\n", jpeg_table_len);
    }

    for (i = 0;  i < num_strips;  i++)
        total_image_len += TIFFRawStripSize(s->tiff.tiff_file, i);
    if ((raw_data = span_alloc(total_image_len)) == NULL)
        return -1;

    total_len = 0;
    if (jpeg_table_len > 0)
        total_len += jpeg_table_len - 4;
    for (i = 0;  i < num_strips;  i++, total_len += len)
    {
        if ((len = TIFFReadRawStrip(s->tiff.tiff_file, i, &raw_data[total_len], total_image_len - total_len)) < 0)
        {
            span_log(&s->logging, SPAN_LOG_WARNING, "%s: TIFFReadRawStrip error.\n", s->tiff.file);
            span_free(raw_data);
            return -1;
        }
    }
    if (jpeg_table_len > 0)
        memcpy(raw_data, jpeg_table, jpeg_table_len - 2);

    if (total_len != total_image_len)
        span_log(&s->logging, SPAN_LOG_FLOW, "Size mismatch %d %d\n", (int) total_len, (int) total_image_len);

    s->tiff.image_size = samples_per_pixel*s->tiff.image_width*s->tiff.image_length;
    if (s->tiff.image_size >= s->tiff.image_buffer_size)
    {
        if ((t = span_realloc(s->tiff.image_buffer, s->tiff.image_size)) == NULL)
        {
            span_free(raw_data);
            return -1;
        }
        s->tiff.image_buffer_size = s->tiff.image_size;
        s->tiff.image_buffer = t;
    }

    t42_decode_init(&t42, packing_row_write_handler, &pack);

    pack.buf = s->tiff.image_buffer;
    pack.ptr = 0;
    pack.row = 0;

    t42_decode_put(&t42, raw_data, total_image_len);
    t42_decode_put(&t42, NULL, 0);

    t42_decode_release(&t42);
    span_free(raw_data);
    return s->tiff.image_size;
}
/*- End of function --------------------------------------------------------*/

static int read_tiff_decompressed_image(t4_tx_state_t *s)
{
    int total_len;
    int len;
    int num_strips;
    int i;
    uint8_t *t;

    /* Decode the whole image into a buffer */
    /* Let libtiff handle the decompression */
    s->tiff.image_size = s->tiff.image_length*TIFFScanlineSize(s->tiff.tiff_file);
    if (s->tiff.image_size >= s->tiff.image_buffer_size)
    {
        if ((t = span_realloc(s->tiff.image_buffer, s->tiff.image_size)) == NULL)
            return -1;
        s->tiff.image_buffer_size = s->tiff.image_size;
        s->tiff.image_buffer = t;
    }

    /* Allow for the image being stored in multiple strips. */
    num_strips = TIFFNumberOfStrips(s->tiff.tiff_file);
    for (i = 0, total_len = 0;  i < num_strips;  i++, total_len += len)
    {
        if ((len = TIFFReadEncodedStrip(s->tiff.tiff_file, i, &s->tiff.image_buffer[total_len], s->tiff.image_size - total_len)) < 0)
        {
            span_log(&s->logging, SPAN_LOG_WARNING, "%s: TIFFReadEncodedStrip error.\n", s->tiff.file);
            return -1;
        }
    }
    /* We might need to flip all the bits, so 1 = black and 0 = white. */
    if (s->tiff.image_type == T4_IMAGE_TYPE_BILEVEL  &&  s->tiff.photo_metric != PHOTOMETRIC_MINISWHITE)
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "%s: Photometric needs swapping.\n", s->tiff.file);
        for (i = 0;  i < s->tiff.image_size;  i++)
            s->tiff.image_buffer[i] = ~s->tiff.image_buffer[i];
        s->tiff.photo_metric = PHOTOMETRIC_MINISWHITE;
    }
    /* We might need to bit reverse each of the bytes of the image. */
    if (s->tiff.fill_order != FILLORDER_LSB2MSB)
        bit_reverse(s->tiff.image_buffer, s->tiff.image_buffer, s->tiff.image_size);
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int read_tiff_image(t4_tx_state_t *s)
{
    int total_len;
    int i;
    int alter_image;
    uint8_t *t;

    if (s->metadata.image_type != s->tiff.image_type  ||  s->metadata.image_width != s->tiff.image_width)
    {
        /* We need to rework the image, so it can't pass directly through */
        alter_image = true;
        image_translate_restart(&s->translator, s->tiff.image_length);
        s->metadata.image_length = image_translate_get_output_length(&s->translator);
        image_translate_set_row_read_handler(&s->translator, translate_row_read2, s);
    }
    else
    {
        alter_image = false;
        s->metadata.image_length = s->tiff.image_length;
    }
    s->pack_buf = NULL;
    s->pack_ptr = 0;
    s->pack_row = 0;

    s->apply_lab = false;
    if (s->tiff.image_type != T4_IMAGE_TYPE_BILEVEL)
    {
        /* If colour/gray scale is supported we may be able to send the image as it is, perhaps after
           a resizing. Otherwise we need to resize it, and squash it to a bilevel image. */
        if (s->tiff.compression == COMPRESSION_JPEG  &&  s->tiff.photo_metric == PHOTOMETRIC_ITULAB)
        {
            if (alter_image)
            {
                if (read_tiff_t42_t81_image(s) < 0)
                    return -1;
                s->pack_buf = s->tiff.image_buffer;
            }
            else
            {
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
            }
        }
#if defined(SPANDSP_SUPPORT_T43)
        else if (s->tiff.compression == COMPRESSION_T43)
        {
            if (alter_image)
            {
                if ( read_tiff_t43_image(s) < 0)
                    return -1;
                s->pack_buf = s->tiff.image_buffer;
            }
            else
            {
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
            }
        }
#endif
#if defined(SPANDSP_SUPPORT_T45)
        else if (s->tiff.compression == COMPRESSION_T45)
        {
            if (alter_image)
            {
                if (read_tiff_t45_image(s) < 0)
                    return -1;
                s->pack_buf = s->tiff.image_buffer;
            }
            else
            {
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
            }
        }
#endif
        else
        {
            /* Let libtiff handle the decompression */
            TIFFSetField(s->tiff.tiff_file, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
            if (alter_image)
            {
                image_translate_set_row_read_handler(&s->translator, translate_row_read, s);
            }
            else
            {
                if (read_tiff_decompressed_image(s) < 0)
                    return -1;
            }
        }

        set_image_width(s, s->metadata.image_width);
        set_image_length(s, s->metadata.image_length);
        t4_tx_set_image_type(s, s->metadata.image_type);
        if (s->metadata.image_type == T4_IMAGE_TYPE_BILEVEL)
        {
            /* We need to dither this image down to pure black and white, possibly resizing it
               along the way. */
            s->tiff.image_size = (s->metadata.image_width*s->metadata.image_length + 7)/8;
            if (s->tiff.image_size >= s->tiff.image_buffer_size)
            {
                if ((t = span_realloc(s->tiff.image_buffer, s->tiff.image_size)) == NULL)
                    return -1;
                s->tiff.image_buffer_size = s->tiff.image_size;
                s->tiff.image_buffer = t;
            }
            s->tiff.raw_row = 0;
            switch (s->tiff.photo_metric)
            {
            case PHOTOMETRIC_CIELAB:
                /* The default luminant is D50 */
                set_lab_illuminant(&s->lab_params, 96.422f, 100.000f,  82.521f);
                set_lab_gamut(&s->lab_params, 0, 100, -128, 127, -128, 127, true);
                s->apply_lab = true;
                break;
            case PHOTOMETRIC_ITULAB:
                /* The default luminant is D50 */
                set_lab_illuminant(&s->lab_params, 96.422f, 100.000f,  82.521f);
                set_lab_gamut(&s->lab_params, 0, 100, -85, 85, -75, 125, false);
                s->apply_lab = true;
                break;
            }
            total_len = 0;
            for (i = 0;  i < s->metadata.image_length;  i++)
                total_len += image_translate_row(&s->translator, &s->tiff.image_buffer[total_len], s->metadata.image_width/8);
            image_translate_release(&s->translator);
            s->row_handler = metadata_row_read_handler;
            s->row_handler_user_data = (void *) s;
        }
        else
        {
            if (alter_image)
            {
                total_len = 0;
                s->tiff.image_buffer = span_realloc(s->tiff.image_buffer, s->metadata.image_width*s->metadata.image_length*3);
                for (i = 0;  i < s->metadata.image_length;  i++)
                    total_len += image_translate_row(&s->translator, &s->tiff.image_buffer[total_len], s->metadata.image_width);
                image_translate_release(&s->translator);
                s->row_handler = metadata_row_read_handler;
                s->row_handler_user_data = (void *) s;
            }
            else
            {
                s->row_handler = tiff_row_read_handler;
                s->row_handler_user_data = (void *) s;
            }
        }
    }
    else
    {
        /* The original image is a bi-level one. We can't really rescale it, as that works out
           really poorly for a bi-level image. It has to be used in its original form. The only
           practical exception is to conver a superfine resolution image to a fine resolution one,
           or a fine image to a standard resolution one. We could pad slightly short rows or crop
           slightly long one, but lets not bother. */
        switch (s->tiff.compression)
        {
#if defined(SPANDSP_SUPPORT_T88)
        case COMPRESSION_T88:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T88:
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
                break;
            default:
                /* libtiff probably cannot decompress T.88, so we must handle it ourselves */
                /* Decode the whole image into a buffer */
                if (read_tiff_t88_image(s) < 0)
                    return -1;
                break;
            }
            break;
#endif
        case COMPRESSION_T85:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T85:
            case T4_COMPRESSION_T85_L0:
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
                break;
            default:
                /* libtiff probably cannot decompress T.85, so we must handle it ourselves */
                /* Decode the whole image into a buffer */
                if (read_tiff_t85_image(s) < 0)
                    return -1;
                break;
            }
            break;
#if 0
        case COMPRESSION_CCITT_T6:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T6:
                /* Read the raw image, and send it as is */
                if (read_tiff_raw_image(s) < 0)
                    return -1;
                break;
            default:
                /* Decode the whole image into a buffer */
                /* Let libtiff handle the decompression */
                if (read_tiff_decompressed_image(s) < 0)
                    return -1;
                break;
            }
            break;
#endif
        default:
            /* Decode the whole image into a buffer */
            /* Let libtiff handle the decompression */
            if (read_tiff_decompressed_image(s) < 0)
                return -1;
            break;
        }
    }
    s->tiff.row = 0;
    return s->metadata.image_length;
}
/*- End of function --------------------------------------------------------*/

static void tiff_tx_release(t4_tx_state_t *s)
{
    if (s->tiff.tiff_file)
    {
        TIFFClose(s->tiff.tiff_file);
        s->tiff.tiff_file = NULL;
        if (s->tiff.file)
            span_free((char *) s->tiff.file);
        s->tiff.file = NULL;
    }
    if (s->tiff.image_buffer)
    {
        span_free(s->tiff.image_buffer);
        s->tiff.image_buffer = NULL;
        s->tiff.image_size = 0;
        s->tiff.image_buffer_size = 0;
    }
}
/*- End of function --------------------------------------------------------*/

static int set_row_read_handler(t4_tx_state_t *s, t4_row_read_handler_t handler, void *user_data)
{
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        return t4_t6_encode_set_row_read_handler(&s->encoder.t4_t6, handler, user_data);
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        return t85_encode_set_row_read_handler(&s->encoder.t85, handler, user_data);
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        return t88_encode_set_row_read_handler(&s->encoder.t88, handler, user_data);
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        return t42_encode_set_row_read_handler(&s->encoder.t42, handler, user_data);
    case T4_COMPRESSION_T43:
        return t43_encode_set_row_read_handler(&s->encoder.t43, handler, user_data);
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        return t45_encode_set_row_read_handler(&s->encoder.t45, handler, user_data);
#endif
    }
    return -1;
}
/*- End of function --------------------------------------------------------*/

static int make_header(t4_tx_state_t *s)
{
    time_t now;
    struct tm tm;
    static const char *months[] =
    {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

    if (s->header_text == NULL)
    {
        if ((s->header_text = span_alloc(132 + 1)) == NULL)
            return -1;
    }
    /* This is very English oriented, but then most FAX machines are, too. Some
       measure of i18n in the time and date, and even the header_info string, is
       entirely possible, although the font area would need some serious work to
       properly deal with East Asian script. There is no spec for what the header
       should contain, or how much of the page it might occupy. The present format
       follows the common practice of a few FAX machines. Nothing more. */
    time(&now);
    if (s->tz)
        tz_localtime(s->tz, &tm, now);
    else
        tm = *localtime(&now);

    snprintf(s->header_text,
             132,
             "  %2d-%s-%d  %02d:%02d    %-50s %-21s   p.%d",
             tm.tm_mday,
             months[tm.tm_mon],
             tm.tm_year + 1900,
             tm.tm_hour,
             tm.tm_min,
             (s->header_info)  ?  s->header_info  :  "",
             (s->local_ident)  ?  s->local_ident  :  "",
             s->current_page + 1);
    return 0;
}
/*- End of function --------------------------------------------------------*/

static int header_row_read_handler(void *user_data, uint8_t buf[], size_t len)
{
    int y_repeats;
    int pattern;
    int pos;
    int row;
    int i;
    char *t;
    t4_tx_state_t *s;

    s = (t4_tx_state_t *) user_data;
    switch (s->metadata.resolution_code)
    {
    default:
    case T4_RESOLUTION_100_100:
        y_repeats = 1;
        break;
    case T4_RESOLUTION_R8_STANDARD:
    case T4_RESOLUTION_200_100:
        y_repeats = 1;
        break;
    case T4_RESOLUTION_R8_FINE:
    case T4_RESOLUTION_200_200:
        y_repeats = 2;
        break;
    case T4_RESOLUTION_300_300:
        y_repeats = 3;
        break;
    case T4_RESOLUTION_R8_SUPERFINE:
    case T4_RESOLUTION_200_400:
        y_repeats = 4;
        break;
    case T4_RESOLUTION_R16_SUPERFINE:
    case T4_RESOLUTION_400_400:
        y_repeats = 4;
        break;
    case T4_RESOLUTION_400_800:
        y_repeats = 8;
        break;
    case T4_RESOLUTION_300_600:
        y_repeats = 6;
        break;
    case T4_RESOLUTION_600_600:
        y_repeats = 6;
        break;
    case T4_RESOLUTION_600_1200:
        y_repeats = 12;
        break;
    case T4_RESOLUTION_1200_1200:
        y_repeats = 12;
        break;
    }
    switch (s->metadata.width_code)
    {
    case T4_SUPPORT_WIDTH_215MM:
        break;
    case T4_SUPPORT_WIDTH_255MM:
        break;
    case T4_SUPPORT_WIDTH_303MM:
        break;
    }
    if (s->header_overlays_image)
    {
        /* Read and dump a row of the real image, allowing for the possibility
           that the real image might end within the header itself */
        if (len != s->row_handler(s->row_handler_user_data, buf, len))
        {
            set_row_read_handler(s, s->row_handler, s->row_handler_user_data);
            return len;
        }
    }
    t = s->header_text;
    row = s->header_row/y_repeats;
    pos = 0;
    switch (s->metadata.image_type)
    {
    case T4_IMAGE_TYPE_BILEVEL:
        for (  ;  *t  &&  pos <= len - 2;  t++)
        {
            pattern = header_font[(uint8_t) *t][row];
            buf[pos++] = (uint8_t) (pattern >> 8);
            buf[pos++] = (uint8_t) (pattern & 0xFF);
        }
        if (pos < len)
            memset(&buf[pos], 0, len - pos);
        break;
    case T4_IMAGE_TYPE_GRAY_8BIT:
        for (  ;  *t  &&  pos <= len - 2;  t++)
        {
            pattern = header_font[(uint8_t) *t][row];
            for (i = 0;  i < 16;  i++)
            {
                buf[pos + i] = (pattern & 0x8000)  ?  0  :  0xFF;
                pattern <<= 1;
            }
            pos += 16;
        }
        if (pos < len)
            memset(&buf[pos], 0xFF, len - pos);
        break;
    case T4_IMAGE_TYPE_COLOUR_8BIT:
        for (  ;  *t  &&  pos <= len - 2;  t++)
        {
            pattern = header_font[(uint8_t) *t][row];
            for (i = 0;  i < 16;  i++)
            {
                buf[pos + 3*i + 0] =
                buf[pos + 3*i + 1] =
                buf[pos + 3*i + 2] = (pattern & 0x8000)  ?  0  :  0xFF;
                pattern <<= 1;
            }
            pos += 3*16;
        }
        if (pos < len)
            memset(&buf[pos], 0xFF, len - pos);
        break;
    case T4_IMAGE_TYPE_COLOUR_BILEVEL:
    case T4_IMAGE_TYPE_4COLOUR_BILEVEL:
    case T4_IMAGE_TYPE_GRAY_12BIT:
    case T4_IMAGE_TYPE_4COLOUR_8BIT:
    case T4_IMAGE_TYPE_COLOUR_12BIT:
    case T4_IMAGE_TYPE_4COLOUR_12BIT:
    default:
        memset(buf, 0xFF, len);
    }
    s->header_row++;
    if (s->header_row >= 16*y_repeats)
    {
        /* End of header. Change to normal image row data. */
        set_row_read_handler(s, s->row_handler, s->row_handler_user_data);
    }
    return len;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_next_page_has_different_format(t4_tx_state_t *s)
{
    span_log(&s->logging, SPAN_LOG_FLOW, "Checking for the existence of page %d\n", s->current_page + 1);
    if (s->current_page >= s->stop_page)
        return -1;
    if (s->tiff.file)
    {
        if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page + 1))
            return -1;
        return test_tiff_directory_info(s);
    }
    return -1;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_set_row_read_handler(t4_tx_state_t *s, t4_row_read_handler_t handler, void *user_data)
{
    s->row_handler = handler;
    s->row_handler_user_data = user_data;
    return set_row_read_handler(s, handler, user_data);
}
/*- End of function --------------------------------------------------------*/

static int release_encoder(t4_tx_state_t *s)
{
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        return t4_t6_encode_release(&s->encoder.t4_t6);
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        return t85_encode_release(&s->encoder.t85);
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        return t88_encode_release(&s->encoder.t88);
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        return t42_encode_release(&s->encoder.t42);
    case T4_COMPRESSION_T43:
        return t43_encode_release(&s->encoder.t43);
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        return t45_encode_release(&s->encoder.t45);
#endif
    }
    return -1;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_set_tx_image_format(t4_tx_state_t *s,
                                            int supported_compressions,
                                            int supported_image_sizes,
                                            int supported_bilevel_resolutions,
                                            int supported_colour_resolutions)
{
    static const struct
    {
        int width;
        int width_code;
        int res_code;           /* Correct resolution code */
        int alt_res_code;       /* Fallback resolution code, where a metric/inch swap is possible */
    } width_and_res_info[] =
    {
        { T4_WIDTH_100_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_100_100,                           0},
        { T4_WIDTH_100_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_100_100,                           0},
        { T4_WIDTH_100_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_100_100,                           0},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_200_100,   T4_RESOLUTION_R8_STANDARD},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_200_200,       T4_RESOLUTION_R8_FINE},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_200_400,  T4_RESOLUTION_R8_SUPERFINE},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,   T4_RESOLUTION_R8_STANDARD,       T4_RESOLUTION_200_100},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_R8_FINE,       T4_RESOLUTION_200_200},
        { T4_WIDTH_200_A4, T4_SUPPORT_WIDTH_215MM,  T4_RESOLUTION_R8_SUPERFINE,       T4_RESOLUTION_200_400},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_200_100,   T4_RESOLUTION_R8_STANDARD},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_200_200,       T4_RESOLUTION_R8_FINE},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_200_400,  T4_RESOLUTION_R8_SUPERFINE},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,   T4_RESOLUTION_R8_STANDARD,       T4_RESOLUTION_200_100},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_R8_FINE,       T4_RESOLUTION_200_200},
        { T4_WIDTH_200_B4, T4_SUPPORT_WIDTH_255MM,  T4_RESOLUTION_R8_SUPERFINE,       T4_RESOLUTION_200_400},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_200_100,   T4_RESOLUTION_R8_STANDARD},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_200_200,       T4_RESOLUTION_R8_FINE},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_200_400,  T4_RESOLUTION_R8_SUPERFINE},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,   T4_RESOLUTION_R8_STANDARD,       T4_RESOLUTION_200_100},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_R8_FINE,       T4_RESOLUTION_200_200},
        { T4_WIDTH_200_A3, T4_SUPPORT_WIDTH_303MM,  T4_RESOLUTION_R8_SUPERFINE,       T4_RESOLUTION_200_400},
        { T4_WIDTH_300_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_300_300,                           0},
        { T4_WIDTH_300_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_300_600,                           0},
        { T4_WIDTH_300_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_300_300,                           0},
        { T4_WIDTH_300_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_300_600,                           0},
        { T4_WIDTH_400_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_400_400, T4_RESOLUTION_R16_SUPERFINE},
        { T4_WIDTH_400_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_400_800,                           0},
        { T4_WIDTH_400_A4, T4_SUPPORT_WIDTH_215MM, T4_RESOLUTION_R16_SUPERFINE,       T4_RESOLUTION_400_400},
        { T4_WIDTH_300_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_300_300,                           0},
        { T4_WIDTH_300_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_300_600,                           0},
        { T4_WIDTH_400_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_400_400, T4_RESOLUTION_R16_SUPERFINE},
        { T4_WIDTH_400_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_400_800,                           0},
        { T4_WIDTH_400_B4, T4_SUPPORT_WIDTH_255MM, T4_RESOLUTION_R16_SUPERFINE,       T4_RESOLUTION_400_400},
        { T4_WIDTH_400_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_400_400, T4_RESOLUTION_R16_SUPERFINE},
        { T4_WIDTH_400_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_400_800,                           0},
        { T4_WIDTH_400_A3, T4_SUPPORT_WIDTH_303MM, T4_RESOLUTION_R16_SUPERFINE,       T4_RESOLUTION_400_400},
        { T4_WIDTH_600_A4, T4_SUPPORT_WIDTH_215MM,       T4_RESOLUTION_600_600,                           0},
        { T4_WIDTH_600_A4, T4_SUPPORT_WIDTH_215MM,      T4_RESOLUTION_600_1200,                           0},
        { T4_WIDTH_600_B4, T4_SUPPORT_WIDTH_255MM,       T4_RESOLUTION_600_600,                           0},
        { T4_WIDTH_600_B4, T4_SUPPORT_WIDTH_255MM,      T4_RESOLUTION_600_1200,                           0},
        { T4_WIDTH_600_A3, T4_SUPPORT_WIDTH_303MM,       T4_RESOLUTION_600_600,                           0},
        { T4_WIDTH_600_A3, T4_SUPPORT_WIDTH_303MM,      T4_RESOLUTION_600_1200,                           0},
        {T4_WIDTH_1200_A4, T4_SUPPORT_WIDTH_215MM,     T4_RESOLUTION_1200_1200,                           0},
        {T4_WIDTH_1200_B4, T4_SUPPORT_WIDTH_255MM,     T4_RESOLUTION_1200_1200,                           0},
        {T4_WIDTH_1200_A3, T4_SUPPORT_WIDTH_303MM,     T4_RESOLUTION_1200_1200,                           0},
        {0x7FFFFFFF, -1, -1, -1}
    };

    static const struct
    {
        int resolution;
        struct
        {
            int resolution;
            int squashing_factor;
        } fallback[4];
    } squashable[4] =
    {
        {
            T4_RESOLUTION_200_400,
            {
                {T4_RESOLUTION_200_200,     2},
                {T4_RESOLUTION_R8_FINE,     2},
                {T4_RESOLUTION_200_100,     4},
                {T4_RESOLUTION_R8_STANDARD, 4}
            }
        },
        {
            T4_RESOLUTION_200_200,
            {
                {T4_RESOLUTION_200_100,     2},
                {T4_RESOLUTION_R8_STANDARD, 2},
                {0,                         0},
                {0,                         0}
            }
        },
        {
            T4_RESOLUTION_R8_SUPERFINE,
            {
                {T4_RESOLUTION_R8_FINE,     2},
                {T4_RESOLUTION_200_200,     2},
                {T4_RESOLUTION_R8_STANDARD, 4},
                {T4_RESOLUTION_200_100,     4}
            }
        },
        {
            T4_RESOLUTION_R8_FINE,
            {
                {T4_RESOLUTION_R8_STANDARD, 2},
                {T4_RESOLUTION_200_100,     2},
                {0,                         0},
                {0,                         0}
            }
        }
    };
    
    int i;
    int j;
    int entry;
    int compression;
    int res;
    int supported_colour_compressions;

    supported_colour_compressions = supported_compressions & (T4_COMPRESSION_T42_T81 | T4_COMPRESSION_T43 | T4_COMPRESSION_T45 | T4_COMPRESSION_SYCC_T81);
    compression = -1;
    s->metadata.image_type = s->tiff.image_type;
    if (s->tiff.image_type != T4_IMAGE_TYPE_BILEVEL)
    {
        span_log(&s->logging, SPAN_LOG_FLOW, "Non-bi-level image\n");
        /* Can we send this page as it is? */
        if (supported_colour_resolutions
            &&
            supported_colour_compressions
            &&
                  (((s->tiff.image_type == T4_IMAGE_TYPE_COLOUR_BILEVEL  ||  s->tiff.image_type == T4_IMAGE_TYPE_COLOUR_8BIT  ||  s->tiff.image_type == T4_IMAGE_TYPE_COLOUR_12BIT)
                    &&
                    (supported_compressions & T4_COMPRESSION_COLOUR))
                ||
                   ((s->tiff.image_type == T4_IMAGE_TYPE_GRAY_8BIT  ||  s->tiff.image_type == T4_IMAGE_TYPE_GRAY_12BIT)
                    &&
                    (supported_compressions & T4_COMPRESSION_GRAYSCALE))))
        {
            /* Gray-scale/colour is possible */
            span_log(&s->logging, SPAN_LOG_FLOW, "Gray-scale/colour is allowed\n");
            /* Choose the best gray-scale/colour encoding available to us */
            if (s->tiff.image_type == T4_IMAGE_TYPE_COLOUR_BILEVEL  &&  (supported_compressions & T4_COMPRESSION_T43))
                compression = T4_COMPRESSION_T43;
            else if ((supported_compressions & T4_COMPRESSION_T42_T81))
                compression = T4_COMPRESSION_T42_T81;
            else if ((supported_compressions & T4_COMPRESSION_T43))
                compression = T4_COMPRESSION_T43;
            else if ((supported_compressions & T4_COMPRESSION_T45))
                compression = T4_COMPRESSION_T45;
            else if ((supported_compressions & T4_COMPRESSION_SYCC_T81))
                compression = T4_COMPRESSION_SYCC_T81;
 
            //best_colour_resolution(s->tiff.x_resolution, supported_colour_resolutions);
        }
        else 
        {
            /* Gray-scale/colour is not possible. Can we flatten the image to send it? */
            span_log(&s->logging, SPAN_LOG_FLOW, "Gray-scale/colour is not allowed\n");
            switch (s->tiff.image_type)
            {
            case T4_IMAGE_TYPE_COLOUR_BILEVEL:
            case T4_IMAGE_TYPE_COLOUR_8BIT:
            case T4_IMAGE_TYPE_COLOUR_12BIT:
                if (!(supported_compressions & T4_COMPRESSION_COLOUR_TO_BILEVEL))
                {
                    span_log(&s->logging, SPAN_LOG_FLOW, "Flattening is not allowed\n");
                    return T4_IMAGE_FORMAT_INCOMPATIBLE;
                }
                break;
            case T4_IMAGE_TYPE_GRAY_8BIT:
            case T4_IMAGE_TYPE_GRAY_12BIT:
                if (!(supported_compressions & T4_COMPRESSION_GRAY_TO_BILEVEL))
                {
                    span_log(&s->logging, SPAN_LOG_FLOW, "Flattening is not allowed\n");
                    return T4_IMAGE_FORMAT_INCOMPATIBLE;
                }
                break;
            }
            /* Squashing to a bi-level image is possible */
            s->metadata.image_type = T4_IMAGE_TYPE_BILEVEL;
            span_log(&s->logging, SPAN_LOG_FLOW, "The image will be flattened to %s\n", t4_image_type_to_str(s->metadata.image_type));
        }
    }

    if (s->metadata.image_type == T4_IMAGE_TYPE_BILEVEL)
    {
        /* Choose the best bi-level encoding available to us */
        if ((supported_compressions & T4_COMPRESSION_T85_L0))
            compression = T4_COMPRESSION_T85_L0;
        else if ((supported_compressions & T4_COMPRESSION_T85))
            compression = T4_COMPRESSION_T85;
        else if ((supported_compressions & T4_COMPRESSION_T6))
            compression = T4_COMPRESSION_T6;
        else if ((supported_compressions & T4_COMPRESSION_T4_2D))
            compression = T4_COMPRESSION_T4_2D;
        else
            compression = T4_COMPRESSION_T4_1D;
    }

    /* Deal with the image width/resolution combination. */
    /* Look for a pattern that matches the image */
    s->metadata.width_code = -1;
    for (entry = 0;  s->tiff.image_width >= width_and_res_info[entry].width;  entry++)
    {
        if (s->tiff.image_width == width_and_res_info[entry].width  &&  s->tiff.resolution_code == width_and_res_info[entry].res_code)
        {
            s->metadata.width_code = width_and_res_info[entry].width_code;
            break;
        }
    }
    s->row_squashing_ratio = 1;
    if (s->metadata.width_code >= 0  &&  (supported_image_sizes & s->metadata.width_code))
    {
        /* We have a valid and supported width/resolution combination */

        /* No resize necessary */
        s->metadata.image_width = s->tiff.image_width;
        s->metadata.image_length = s->tiff.image_length;

        res = T4_IMAGE_FORMAT_NORESSUPPORT;
        if (s->metadata.image_type == T4_IMAGE_TYPE_BILEVEL)
        {
            if ((width_and_res_info[entry].res_code & supported_bilevel_resolutions))
            {
                /* We can use the resolution of the original image */
                s->metadata.resolution_code = s->tiff.resolution_code;
                s->metadata.x_resolution = code_to_x_resolution(s->metadata.resolution_code);
                s->metadata.y_resolution = code_to_y_resolution(s->metadata.resolution_code);
                res = T4_IMAGE_FORMAT_OK;
            }
            else if ((width_and_res_info[entry].alt_res_code & supported_bilevel_resolutions))
            {
                /* We can do a metric/imperial swap, and have a usable resolution */
                span_log(&s->logging,
                         SPAN_LOG_FLOW,
                         "Image resolution %s falls back to %s\n",
                         t4_image_resolution_to_str(s->tiff.resolution_code),
                         t4_image_resolution_to_str(width_and_res_info[entry].alt_res_code));
                s->metadata.resolution_code = width_and_res_info[entry].alt_res_code;
                s->metadata.x_resolution = code_to_x_resolution(s->metadata.resolution_code);
                s->metadata.y_resolution = code_to_y_resolution(s->metadata.resolution_code);
                res = T4_IMAGE_FORMAT_OK;
            }
            else
            {
                if (s->tiff.image_type == T4_IMAGE_TYPE_BILEVEL)
                {
                    if ((s->tiff.resolution_code & (T4_RESOLUTION_200_400 | T4_RESOLUTION_200_200 | T4_RESOLUTION_R8_SUPERFINE | T4_RESOLUTION_R8_FINE)))
                    {
                        /* This might be a resolution we can squash down to something which is supported */
                        for (i = 0;  i < 4;  i++)
                        {
                            if ((s->tiff.resolution_code & squashable[i].resolution))
                                break;
                        }
                        if (i < 4)
                        {
                            /* This is a squashable resolution, so let's see if there is a valid
                               fallback we can squash the image to, scanning through the entries
                               in their order of preference. */
                            for (j = 0;  j < 4;  j++)
                            {
                                if ((supported_bilevel_resolutions & squashable[i].fallback[j].resolution))
                                {
                                    span_log(&s->logging,
                                             SPAN_LOG_FLOW,
                                             "Image resolution %s falls back to %s\n",
                                             t4_image_resolution_to_str(s->tiff.resolution_code),
                                             t4_image_resolution_to_str(squashable[i].fallback[j].resolution));
                                    s->row_squashing_ratio = squashable[i].fallback[j].squashing_factor;
                                    s->metadata.resolution_code = squashable[i].fallback[j].resolution;
                                    s->metadata.x_resolution = code_to_x_resolution(s->metadata.resolution_code);
                                    s->metadata.y_resolution = code_to_y_resolution(s->metadata.resolution_code);
                                    res = T4_IMAGE_FORMAT_OK;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            /* If we have not succeeded in matching up the size and resolution, the next step will
               depend on whether the original was a bi-level image. If it was, we are stuck, as you can't
               really resize those. If it was not, a resize might be possible */
            if (res != T4_IMAGE_FORMAT_OK)
            {
                if (s->tiff.image_type == T4_IMAGE_TYPE_BILEVEL)
                    return T4_IMAGE_FORMAT_NORESSUPPORT;
                if (!(supported_compressions & T4_COMPRESSION_RESCALING))
                    return T4_IMAGE_FORMAT_NOSIZESUPPORT;
            }
            /* TODO */
        }
        else
        {
            if ((width_and_res_info[entry].res_code & supported_bilevel_resolutions))
            {
                if ((s->tiff.resolution_code & supported_colour_resolutions))
                {
                    /* We can use the resolution of the original image */
                    s->metadata.resolution_code = width_and_res_info[entry].res_code;
                    s->metadata.x_resolution = code_to_x_resolution(s->metadata.resolution_code);
                    s->metadata.y_resolution = code_to_y_resolution(s->metadata.resolution_code);
                    res = T4_IMAGE_FORMAT_OK;
                }
            }
        }
    }
    else
    {
        /* Can we rework the image to fit? */
        /* We can't rework a bilevel image that fits none of the patterns */
        if (s->tiff.image_type == T4_IMAGE_TYPE_BILEVEL  ||  s->tiff.image_type == T4_IMAGE_TYPE_COLOUR_BILEVEL)
            return T4_IMAGE_FORMAT_NORESSUPPORT;
        if (!(supported_compressions & T4_COMPRESSION_RESCALING))
            return T4_IMAGE_FORMAT_NOSIZESUPPORT;
        /* Any other kind of image might be resizable */
        res = T4_IMAGE_FORMAT_OK;
        /* TODO: use more sophisticated resizing */
        s->metadata.image_width = T4_WIDTH_200_A4;
        s->metadata.resolution_code = T4_RESOLUTION_200_200;
        s->metadata.x_resolution = code_to_x_resolution(s->metadata.resolution_code);
        s->metadata.y_resolution = code_to_y_resolution(s->metadata.resolution_code);
    }

    if (res != T4_IMAGE_FORMAT_OK)
        return res;

    if (s->metadata.image_type != s->tiff.image_type  ||  s->metadata.image_width != s->tiff.image_width)
    {
        if (image_translate_init(&s->translator,
                                 s->metadata.image_type,
                                 s->metadata.image_width,
                                 -1,
                                 s->tiff.image_type,
                                 s->tiff.image_width,
                                 s->tiff.image_length,
                                 translate_row_read2,
                                 s) == NULL)
        {
            return T4_IMAGE_FORMAT_INCOMPATIBLE;
        }
        s->metadata.image_length = image_translate_get_output_length(&s->translator);
    }

    if (compression != s->metadata.compression)
    {
        switch (compression)
        {
        case T4_COMPRESSION_T4_1D:
        case T4_COMPRESSION_T4_2D:
        case T4_COMPRESSION_T6:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T4_1D:
            case T4_COMPRESSION_T4_2D:
            case T4_COMPRESSION_T6:
                break;
            default:
                release_encoder(s);
                t4_t6_encode_init(&s->encoder.t4_t6, compression, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            if (t4_t6_encode_set_encoding(&s->encoder.t4_t6, compression))
                res = -1;
            break;
        case T4_COMPRESSION_T85:
        case T4_COMPRESSION_T85_L0:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T85:
            case T4_COMPRESSION_T85_L0:
                break;
            default:
                release_encoder(s);
                t85_encode_init(&s->encoder.t85, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            break;
#if defined(SPANDSP_SUPPORT_T88)
        case T4_COMPRESSION_T88:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T88:
                break;
            default:
                t88_encode_init(&s->encoder.t88, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            break;
#endif
        case T4_COMPRESSION_T42_T81:
        case T4_COMPRESSION_SYCC_T81:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T42_T81:
            case T4_COMPRESSION_SYCC_T81:
                break;
            default:
                release_encoder(s);
                t42_encode_init(&s->encoder.t42, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            break;
        case T4_COMPRESSION_T43:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T43:
                break;
            default:
                release_encoder(s);
                t43_encode_init(&s->encoder.t43, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            break;
#if defined(SPANDSP_SUPPORT_T45)
        case T4_COMPRESSION_T45:
            switch (s->metadata.compression)
            {
            case T4_COMPRESSION_T45:
                break;
            default:
                release_encoder(s);
                t45_encode_init(&s->encoder.t45, s->metadata.image_width, s->metadata.image_length, s->row_handler, s->row_handler_user_data);
                break;
            }
            s->metadata.compression = compression;
            res = T4_IMAGE_FORMAT_OK;
            break;
#endif
        }
    }

    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_set_max_2d_rows_per_1d_row(&s->encoder.t4_t6, -s->metadata.y_resolution);
        break;
    }

    set_image_width(s, s->metadata.image_width);
    set_image_length(s, s->metadata.image_length);
    t4_tx_set_image_type(s, s->metadata.image_type);
    return res;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_compression(t4_tx_state_t *s)
{
    return s->metadata.compression;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_image_type(t4_tx_state_t *s)
{
    return s->metadata.image_type;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_resolution(t4_tx_state_t *s)
{
    return s->metadata.resolution_code;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_x_resolution(t4_tx_state_t *s)
{
    return s->metadata.x_resolution;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_y_resolution(t4_tx_state_t *s)
{
    return s->metadata.y_resolution;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_image_width(t4_tx_state_t *s)
{
    return s->metadata.image_width;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_tx_image_width_code(t4_tx_state_t *s)
{
    return s->metadata.width_code;
}
/*- End of function --------------------------------------------------------*/

static void set_image_width(t4_tx_state_t *s, uint32_t image_width)
{
    s->metadata.image_width = image_width;
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_set_image_width(&s->encoder.t4_t6, image_width);
        break;
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        t85_encode_set_image_width(&s->encoder.t85, image_width);
        break;
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        t88_encode_set_image_width(&s->encoder.t88, image_width);
        break;
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        t42_encode_set_image_width(&s->encoder.t42, image_width);
        break;
    case T4_COMPRESSION_T43:
        t43_encode_set_image_width(&s->encoder.t43, image_width);
        break;
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        t45_encode_set_image_width(&s->encoder.t45, image_width);
        break;
#endif
    }
}
/*- End of function --------------------------------------------------------*/

static void set_image_length(t4_tx_state_t *s, uint32_t image_length)
{
    s->metadata.image_length = image_length;
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_set_image_length(&s->encoder.t4_t6, image_length);
        break;
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        t85_encode_set_image_length(&s->encoder.t85, image_length);
        break;
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        t88_encode_set_image_length(&s->encoder.t88, image_length);
        break;
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        t42_encode_set_image_length(&s->encoder.t42, image_length);
        break;
    case T4_COMPRESSION_T43:
        t43_encode_set_image_length(&s->encoder.t43, image_length);
        break;
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        t45_encode_set_image_length(&s->encoder.t45, image_length);
        break;
#endif
    }
}
/*- End of function --------------------------------------------------------*/

static void t4_tx_set_image_type(t4_tx_state_t *s, int image_type)
{
    s->metadata.image_type = image_type;
    switch (s->metadata.compression)
    {
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        t88_encode_set_image_type(&s->encoder.t88, image_type);
        break;
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        t42_encode_set_image_type(&s->encoder.t42, image_type);
        break;
    case T4_COMPRESSION_T43:
        t43_encode_set_image_type(&s->encoder.t43, image_type);
        break;
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        t45_encode_set_image_type(&s->encoder.t45, image_type);
        break;
#endif
    }
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_min_bits_per_row(t4_tx_state_t *s, int bits)
{
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_set_min_bits_per_row(&s->encoder.t4_t6, bits);
        break;
    }
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_max_2d_rows_per_1d_row(t4_tx_state_t *s, int max)
{
    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_set_max_2d_rows_per_1d_row(&s->encoder.t4_t6, max);
        break;
    }
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_header_overlays_image(t4_tx_state_t *s, bool header_overlays_image)
{
    s->header_overlays_image = header_overlays_image;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_local_ident(t4_tx_state_t *s, const char *ident)
{
    s->local_ident = (ident  &&  ident[0])  ?  ident  :  NULL;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_header_info(t4_tx_state_t *s, const char *info)
{
    s->header_info = (info  &&  info[0])  ?  info  :  NULL;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_set_header_tz(t4_tx_state_t *s, struct tz_s *tz)
{
    s->tz = tz;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_pages_in_file(t4_tx_state_t *s)
{
    int max;

    if (s->tiff.file)
        max = get_tiff_total_pages(s);
    else
        max = 1;
    if (max >= 0)
        s->tiff.pages_in_file = max;
    return max;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_current_page_in_file(t4_tx_state_t *s)
{
    return s->current_page;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(void) t4_tx_get_transfer_statistics(t4_tx_state_t *s, t4_stats_t *t)
{
    memset(t, 0, sizeof(*t));
    t->pages_transferred = s->current_page - s->start_page;
    t->pages_in_file = s->tiff.pages_in_file;

    t->image_type = s->tiff.image_type;
    t->image_width = s->tiff.image_width;
    t->image_length = s->tiff.image_length;

    t->image_x_resolution = s->tiff.x_resolution;
    t->image_y_resolution = s->tiff.y_resolution;
    t->x_resolution = s->metadata.x_resolution;
    t->y_resolution = s->metadata.y_resolution;

    t->type = s->metadata.image_type;
    t->compression = s->metadata.compression;

    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t->width = t4_t6_encode_get_image_width(&s->encoder.t4_t6);
        t->length = t4_t6_encode_get_image_length(&s->encoder.t4_t6);
        t->line_image_size = t4_t6_encode_get_compressed_image_size(&s->encoder.t4_t6)/8;
        break;
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        t->width = t85_encode_get_image_width(&s->encoder.t85);
        t->length = t85_encode_get_image_length(&s->encoder.t85);
        t->line_image_size = t85_encode_get_compressed_image_size(&s->encoder.t85)/8;
        break;
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        t->width = t88_encode_get_image_width(&s->encoder.t88);
        t->length = t88_encode_get_image_length(&s->encoder.t88);
        t->line_image_size = t88_encode_get_compressed_image_size(&s->encoder.t88)/8;
        break;
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        t->width = t42_encode_get_image_width(&s->encoder.t42);
        t->length = t42_encode_get_image_length(&s->encoder.t42);
        t->line_image_size = t42_encode_get_compressed_image_size(&s->encoder.t42)/8;
        break;
    case T4_COMPRESSION_T43:
        t->width = t43_encode_get_image_width(&s->encoder.t43);
        t->length = t43_encode_get_image_length(&s->encoder.t43);
        t->line_image_size = t43_encode_get_compressed_image_size(&s->encoder.t43)/8;
        break;
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        t->width = t45_encode_get_image_width(&s->encoder.t45);
        t->length = t45_encode_get_image_length(&s->encoder.t45);
        t->line_image_size = t45_encode_get_compressed_image_size(&s->encoder.t45)/8;
        break;
#endif
    }
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_image_complete(t4_tx_state_t *s)
{
    if (s->no_encoder.buf_len > 0)
    {
        if (s->no_encoder.buf_ptr >= s->no_encoder.buf_len)
            return SIG_STATUS_END_OF_DATA;
        return 0;
    }

    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        return t4_t6_encode_image_complete(&s->encoder.t4_t6);
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        return t85_encode_image_complete(&s->encoder.t85);
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        return t88_encode_image_complete(&s->encoder.t88);
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        return t42_encode_image_complete(&s->encoder.t42);
    case T4_COMPRESSION_T43:
        return t43_encode_image_complete(&s->encoder.t43);
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        return t45_encode_image_complete(&s->encoder.t45);
#endif
    }
    return SIG_STATUS_END_OF_DATA;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get_bit(t4_tx_state_t *s)
{
    int bit;

    /* We only get bit by bit for T.4 1D and T.4 2-D. */
    if (s->no_encoder.buf_len > 0)
    {
        if (s->no_encoder.buf_ptr >= s->no_encoder.buf_len)
            return SIG_STATUS_END_OF_DATA;
        bit = (s->no_encoder.buf[s->no_encoder.buf_ptr] >> s->no_encoder.bit) & 1;
        if (++s->no_encoder.bit >= 8)
        {
            s->no_encoder.bit = 0;
            s->no_encoder.buf_ptr++;
        }
        return bit;
    }
    return t4_t6_encode_get_bit(&s->encoder.t4_t6);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_get(t4_tx_state_t *s, uint8_t buf[], size_t max_len)
{
    if (s->no_encoder.buf_len > 0)
    {
        if (max_len > (s->no_encoder.buf_len - s->no_encoder.buf_ptr))
            max_len = s->no_encoder.buf_len - s->no_encoder.buf_ptr;
        memcpy(buf, &s->no_encoder.buf[s->no_encoder.buf_ptr], max_len);
        s->no_encoder.buf_ptr += max_len;
        return max_len;
    }

    if (s->image_get_handler)
        return s->image_get_handler((void *) &s->encoder, buf, max_len);

    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_start_page(t4_tx_state_t *s)
{
    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx page %d - compression %s\n", s->current_page, t4_compression_to_str(s->metadata.compression));
    if (s->current_page > s->stop_page)
        return -1;
    if (s->tiff.file)
    {
        if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page))
            return -1;
        get_tiff_directory_info(s);
        if (read_tiff_image(s) < 0)
            return -1;
    }
    else
    {
        s->metadata.image_length = UINT32_MAX;
    }

    switch (s->metadata.compression)
    {
    case T4_COMPRESSION_T4_1D:
    case T4_COMPRESSION_T4_2D:
    case T4_COMPRESSION_T6:
        t4_t6_encode_restart(&s->encoder.t4_t6, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t4_t6_encode_get;
        break;
    case T4_COMPRESSION_T85:
    case T4_COMPRESSION_T85_L0:
        t85_encode_restart(&s->encoder.t85, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t85_encode_get;
        break;
#if defined(SPANDSP_SUPPORT_T88)
    case T4_COMPRESSION_T88:
        t88_encode_restart(&s->encoder.t88, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t88_encode_get;
        break;
#endif
    case T4_COMPRESSION_T42_T81:
    case T4_COMPRESSION_SYCC_T81:
        t42_encode_restart(&s->encoder.t42, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t42_encode_get;
        break;
    case T4_COMPRESSION_T43:
        t43_encode_restart(&s->encoder.t43, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t43_encode_get;
        break;
#if defined(SPANDSP_SUPPORT_T45)
    case T4_COMPRESSION_T45:
        t45_encode_restart(&s->encoder.t45, s->metadata.image_width, s->metadata.image_length);
        s->image_get_handler = (t4_image_get_handler_t) t45_encode_get;
        break;
#endif
    default:
        s->image_get_handler = NULL;
        break;
    }

    /* If there is a page header, create that first */
    //if (s->metadata.image_type == T4_IMAGE_TYPE_BILEVEL  &&  s->header_info  &&  s->header_info[0]  &&  make_header(s) == 0)
    if (s->header_info  &&  s->header_info[0]  &&  make_header(s) == 0)
    {
        s->header_row = 0;
        set_row_read_handler(s, header_row_read_handler, (void *) s);
    }
    else
    {
        set_row_read_handler(s, s->row_handler, s->row_handler_user_data);
    }
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_restart_page(t4_tx_state_t *s)
{
    /* This is currently the same as starting a page, but keep it a separate call,
       as the two things might diverge a little in the future. */
    return t4_tx_start_page(s);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_end_page(t4_tx_state_t *s)
{
    s->current_page++;
    return 0;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(logging_state_t *) t4_tx_get_logging_state(t4_tx_state_t *s)
{
    return &s->logging;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(t4_tx_state_t *) t4_tx_init(t4_tx_state_t *s, const char *file, int start_page, int stop_page)
{
    int allocated;

    allocated = false;
    if (s == NULL)
    {
        if ((s = (t4_tx_state_t *) span_alloc(sizeof(*s))) == NULL)
            return NULL;
        allocated = true;
    }
    memset(s, 0, sizeof(*s));
#if defined(SPANDSP_SUPPORT_TIFF_FX)
    TIFF_FX_init();
#endif
    span_log_init(&s->logging, SPAN_LOG_NONE, NULL);
    span_log_set_protocol(&s->logging, "T.4");

    span_log(&s->logging, SPAN_LOG_FLOW, "Start tx document\n");

    s->current_page =
    s->start_page = (start_page >= 0)  ?  start_page  :  0;
    s->stop_page = (stop_page >= 0)  ?  stop_page  :  INT_MAX;
    s->metadata.compression = T4_COMPRESSION_NONE;

    s->row_handler = tiff_row_read_handler;
    s->row_handler_user_data = (void *) s;

    s->row_squashing_ratio = 1;

    if (file)
    {
        if (open_tiff_input_file(s, file) < 0)
        {
            if (allocated)
                span_free(s);
            return NULL;
        }
        s->tiff.file = strdup(file);
        s->tiff.pages_in_file = -1;
        if (!TIFFSetDirectory(s->tiff.tiff_file, (tdir_t) s->current_page)
            ||
            get_tiff_directory_info(s))
        {
            tiff_tx_release(s);
            if (allocated)
                span_free(s);
            return NULL;
        }
    }
    return s;
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_release(t4_tx_state_t *s)
{
    if (s->tiff.file)
        tiff_tx_release(s);
    if (s->header_text)
    {
        span_free(s->header_text);
        s->header_text = NULL;
    }
    if (s->colour_map)
    {
        span_free(s->colour_map);
        s->colour_map = NULL;
    }
    return release_encoder(s);
}
/*- End of function --------------------------------------------------------*/

SPAN_DECLARE(int) t4_tx_free(t4_tx_state_t *s)
{
    int ret;

    ret = t4_tx_release(s);
    span_free(s);
    return ret;
}
/*- End of function --------------------------------------------------------*/
/*- End of file ------------------------------------------------------------*/
